Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
In dit onderwerp wordt beschreven hoe u een asynchrone methode implementeert in Microsoft Media Foundation.
Asynchrone methoden zijn alomtegenwoordig in de Media Foundation-pijplijn. Asynchrone methoden maken het eenvoudiger om werk over verschillende threads te verdelen. Het is met name belangrijk om I/O asynchroon uit te voeren, zodat het lezen van een bestand of netwerk de rest van de pijplijn niet blokkeert.
Als u een mediabron of mediasink schrijft, is het van cruciaal belang om asynchrone bewerkingen correct af te handelen, omdat de prestaties van uw component invloed hebben op de hele pijplijn.
Notitie
Media Foundation-transformaties (MFT's) maken standaard gebruik van synchrone methoden.
Werkwachtrijen voor asynchrone bewerkingen
In Media Foundation is er een nauwe relatie tussen asynchrone callbackmethoden en werkwachtrijen. Een werkwachtrij is een abstractie voor het verplaatsen van werk van de thread van de beller naar een worker thread. Ga als volgt te werk om werk uit te voeren in een werkwachtrij:
Implementeer de interface IMFAsyncCallback.
Roep MFCreateAsyncResult aan om een resultaatobject te maken. Het resultaatobject maakt de IMFAsyncResultbeschikbaar. Het resultaatobject bevat drie aanwijzers:
- Een aanwijzer naar de IMFAsyncCallback interface van de beller.
- Een optionele aanwijzer naar een statusobject. Indien opgegeven, moet het statusobject IUnknown-implementeren.
- Een optionele aanwijzer naar een privéobject. Indien opgegeven, moet dit object ook IUnknown-implementeren.
De laatste twee aanwijzers kunnen NULLzijn . Gebruik ze anders om informatie over de asynchrone bewerking op te slaan.
Roep MFPutWorkItemEx aan om het werkitem in de wachtrij te plaatsen.
De thread in de werkwachtrij roept uw IMFAsyncCallback::Invoke methode aan.
Voer het werk binnen uw -aanroepmethode uit. De parameter pAsyncResult van deze methode is de IMFAsyncResult aanwijzer uit stap 2. Gebruik deze aanwijzer om het statusobject en het privéobject op te halen:
- Als u het statusobject wilt ophalen, roept u IMFAsyncResult::GetStateaan.
- Als u het privéobject wilt ophalen, roept u IMFAsyncResult::GetObjectaan.
Als alternatief kunt u stap 2 en 3 combineren door de functie MFPutWorkItem aan te roepen. Intern roept deze functie MFCreateAsyncResult- aan om het resultaatobject te maken.
In het volgende diagram ziet u de relaties tussen de aanroeper, het resultaatobject, het statusobject en het privéobject.
In het volgende sequentiediagram ziet u hoe een object een werkitem in de wachtrij zet. Wanneer de thread in de werkwachtrij Aanroepenaanroept, wordt de asynchrone bewerking op die thread uitgevoerd.
Het is belangrijk om te onthouden dat Invoke wordt aangeroepen vanuit een thread die eigendom is van de werkwachtrij. Uw implementatie van aanroepen moet thread-veilig zijn. "Als u bovendien de werkwachtrij van het platform (MFASYNC_CALLBACK_QUEUE_STANDARD) gebruikt, is het essentieel dat u de thread nooit blokkeert, omdat dit de volledige Media Foundation-pijplijn kan verhinderen om gegevens te verwerken." Als u een bewerking wilt uitvoeren die wordt geblokkeerd of lang duurt, gebruikt u een privéwerkwachtrij. Als u een privéwerkwachtrij wilt maken, roept u MFAllocateWorkQueueaan. Elk pijplijnonderdeel dat I/O-bewerkingen uitvoert, moet voorkomen dat I/O-aanroepen om dezelfde reden worden geblokkeerd. De interface IMFByteStream biedt een nuttige abstractie voor asynchrone bestands-I/O.
Het Begin.../Einde... patroon implementeren
Zoals beschreven in Asynchrone methodenaanroepen, gebruiken asynchrone methoden in Media Foundation vaak de Begin.../End.... patroon. In dit patroon gebruikt een asynchrone bewerking twee methoden met handtekeningen die vergelijkbaar zijn met de volgende:
// Starts the asynchronous operation.
HRESULT BeginX(IMFAsyncCallback *pCallback, IUnknown *punkState);
// Completes the asynchronous operation.
// Call this method from inside the caller's Invoke method.
HRESULT EndX(IMFAsyncResult *pResult);
Als u de methode echt asynchroon wilt maken, moet de implementatie van BeginX- het werkelijke werk uitvoeren op een andere thread. Hier komen werkwachtrijen in beeld. In de volgende stappen is de aanroeper de code die BeginX- en EndX-aanroept. Dit kan een toepassing of de Media Foundation-pijplijn zijn. Het -onderdeel is de code waarmee BeginX- en EndX-worden geïmplementeerd.
- De beller roept Begin op..., door een verwijzing te geven aan de IMFAsyncCallback-interface van de beller.
- Het onderdeel maakt een nieuw asynchroon resultaatobject. Dit object slaat de callback-interface en het statusobject van de aanroeper op. Normaal gesproken worden alle persoonlijke statusgegevens opgeslagen die het onderdeel nodig heeft om de bewerking te voltooien. Het resultaatobject uit deze stap heeft het label Resultaat 1 in het volgende diagram.
- Het onderdeel maakt een tweede resultaatobject. In dit resultaatobject worden twee aanwijzers opgeslagen: het eerste resultaatobject en de callback-interface van de aanwijzer. Dit resultaatobject heeft het label Resultaat 2 in het volgende diagram.
- Het onderdeel roept MFPutWorkItemEx- aan om een nieuw werkitem in de wachtrij te plaatsen.
- In de methode Aanroepen doet het onderdeel het asynchrone werk.
- Het onderdeel roept MFInvokeCallback- aan om de callback-methode van de beller aan te roepen.
- De aanroeper roept de methode EndX- aan.
Voorbeeld van asynchrone methode
Ter illustratie van deze discussie gebruiken we een kunstmatig voorbeeld. Overweeg een asynchrone methode voor het berekenen van een vierkantswortel:
HRESULT BeginSquareRoot(double x, IMFAsyncCallback *pCB, IUnknown *pState);
HRESULT EndSquareRoot(IMFAsyncResult *pResult, double *pVal);
De parameter x van BeginSquareRoot is de waarde waarvan de vierkantswortel wordt berekend. De vierkantswortel wordt geretourneerd in de parameter pVal van EndSquareRoot.
Hier volgt de declaratie van een klasse die deze twee methoden implementeert:
class SqrRoot : public IMFAsyncCallback
{
LONG m_cRef;
double m_sqrt;
HRESULT DoCalculateSquareRoot(AsyncOp *pOp);
public:
SqrRoot() : m_cRef(1)
{
}
HRESULT BeginSquareRoot(double x, IMFAsyncCallback *pCB, IUnknown *pState);
HRESULT EndSquareRoot(IMFAsyncResult *pResult, double *pVal);
// IUnknown methods.
STDMETHODIMP QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] =
{
QITABENT(SqrRoot, IMFAsyncCallback),
{ 0 }
};
return QISearch(this, qit, riid, ppv);
}
STDMETHODIMP_(ULONG) AddRef()
{
return InterlockedIncrement(&m_cRef);
}
STDMETHODIMP_(ULONG) Release()
{
LONG cRef = InterlockedDecrement(&m_cRef);
if (cRef == 0)
{
delete this;
}
return cRef;
}
// IMFAsyncCallback methods.
STDMETHODIMP GetParameters(DWORD* pdwFlags, DWORD* pdwQueue)
{
// Implementation of this method is optional.
return E_NOTIMPL;
}
// Invoke is where the work is performed.
STDMETHODIMP Invoke(IMFAsyncResult* pResult);
};
De klasse SqrRoot implementeert IMFAsyncCallback, zodat de vierkantswortelbewerking in een werkwachtrij kan worden geplaatst. De DoCalculateSquareRoot methode is de privéklassemethode waarmee de vierkantswortel wordt berekend. Deze methode wordt aangeroepen vanuit de thread van de werkwachtrij.
Eerst hebben we een manier nodig om de waarde van xop te slaan, zodat deze kan worden opgehaald wanneer de werkwachtrijthread SqrRoot::Invokeaanroept. Hier volgt een eenvoudige klasse waarin de informatie wordt opgeslagen:
class AsyncOp : public IUnknown
{
LONG m_cRef;
public:
double m_value;
AsyncOp(double val) : m_cRef(1), m_value(val) { }
STDMETHODIMP QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] =
{
QITABENT(AsyncOp, IUnknown),
{ 0 }
};
return QISearch(this, qit, riid, ppv);
}
STDMETHODIMP_(ULONG) AddRef()
{
return InterlockedIncrement(&m_cRef);
}
STDMETHODIMP_(ULONG) Release()
{
LONG cRef = InterlockedDecrement(&m_cRef);
if (cRef == 0)
{
delete this;
}
return cRef;
}
};
Deze klasse implementeert IUnknown-, zodat deze kan worden opgeslagen in een resultaatobject.
Met de volgende code wordt de methode BeginSquareRoot geïmplementeerd:
HRESULT SqrRoot::BeginSquareRoot(double x, IMFAsyncCallback *pCB, IUnknown *pState)
{
AsyncOp *pOp = new (std::nothrow) AsyncOp(x);
if (pOp == NULL)
{
return E_OUTOFMEMORY;
}
IMFAsyncResult *pResult = NULL;
// Create the inner result object. This object contains pointers to:
//
// 1. The caller's callback interface and state object.
// 2. The AsyncOp object, which contains the operation data.
//
HRESULT hr = MFCreateAsyncResult(pOp, pCB, pState, &pResult);
if (SUCCEEDED(hr))
{
// Queue a work item. The work item contains pointers to:
//
// 1. The callback interface of the SqrRoot object.
// 2. The inner result object.
hr = MFPutWorkItem(MFASYNC_CALLBACK_QUEUE_STANDARD, this, pResult);
pResult->Release();
}
return hr;
}
Deze code doet het volgende:
- Maakt een nieuw exemplaar van de
AsyncOp-klasse om de waarde van xop te slaan. - Roept MFCreateAsyncResult- aan om een resultaatobject te maken. Dit object bevat verschillende aanwijzers:
- Een aanwijzer naar de IMFAsyncCallback interface van de beller.
- Een aanwijzer naar het statusobject van de aanroeper (pState).
- Een aanwijzer naar het
AsyncOp-object.
- Roept MFPutWorkItem- aan om een nieuw werkitem in de wachtrij te plaatsen. Met deze aanroep wordt impliciet een buitenste resultaatobject gemaakt dat de volgende aanwijzers bevat:
- Een aanwijzer naar de IMFAsyncCallback interface van het
SqrRootobject. - Een aanwijzer naar het binnenste resultaatobject uit stap 2.
- Een aanwijzer naar de IMFAsyncCallback interface van het
Met de volgende code wordt de methode SqrRoot::Invoke geïmplementeerd:
// Invoke is called by the work queue. This is where the object performs the
// asynchronous operation.
STDMETHODIMP SqrRoot::Invoke(IMFAsyncResult* pResult)
{
HRESULT hr = S_OK;
IUnknown *pState = NULL;
IUnknown *pUnk = NULL;
IMFAsyncResult *pCallerResult = NULL;
AsyncOp *pOp = NULL;
// Get the asynchronous result object for the application callback.
hr = pResult->GetState(&pState);
if (FAILED(hr))
{
goto done;
}
hr = pState->QueryInterface(IID_PPV_ARGS(&pCallerResult));
if (FAILED(hr))
{
goto done;
}
// Get the object that holds the state information for the asynchronous method.
hr = pCallerResult->GetObject(&pUnk);
if (FAILED(hr))
{
goto done;
}
pOp = static_cast<AsyncOp*>(pUnk);
// Do the work.
hr = DoCalculateSquareRoot(pOp);
done:
// Signal the application.
if (pCallerResult)
{
pCallerResult->SetStatus(hr);
MFInvokeCallback(pCallerResult);
}
SafeRelease(&pState);
SafeRelease(&pUnk);
SafeRelease(&pCallerResult);
return S_OK;
}
Met deze methode worden het binnenste resultaatobject en het AsyncOp-object opgehaald. Vervolgens wordt het AsyncOp object doorgegeven aan DoCalculateSquareRoot. Ten slotte wordt IMFAsyncResult::SetStatus aangeroepen om de statuscode en MFInvokeCallback- in te stellen om de callbackmethode van de beller aan te roepen.
De methode DoCalculateSquareRoot doet precies wat u zou verwachten:
HRESULT SqrRoot::DoCalculateSquareRoot(AsyncOp *pOp)
{
pOp->m_value = sqrt(pOp->m_value);
return S_OK;
}
Wanneer de callback-methode van de aanroeper wordt aangeroepen, is het de verantwoordelijkheid van de beller om de methode End... aan te roepen, in dit geval EndSquareRoot. De EndSquareRoot is hoe de aanroeper het resultaat van de asynchrone bewerking ophaalt, die in dit voorbeeld de berekende vierkantswortel is. Deze informatie wordt opgeslagen in het resultaatobject:
HRESULT SqrRoot::EndSquareRoot(IMFAsyncResult *pResult, double *pVal)
{
*pVal = 0;
IUnknown *pUnk = NULL;
HRESULT hr = pResult->GetStatus();
if (FAILED(hr))
{
goto done;
}
hr = pResult->GetObject(&pUnk);
if (FAILED(hr))
{
goto done;
}
AsyncOp *pOp = static_cast<AsyncOp*>(pUnk);
// Get the result.
*pVal = pOp->m_value;
done:
SafeRelease(&pUnk);
return hr;
}
Operatiewachtrijen
Tot nu toe is ervan uitgegaan dat een asynchrone bewerking op elk gewenst moment kan worden uitgevoerd, ongeacht de huidige status van het object. Bedenk bijvoorbeeld wat er gebeurt als een toepassing BeginSquareRoot aanroept terwijl een eerdere aanroep naar dezelfde methode nog in behandeling is. De SqrRoot klasse kan het nieuwe werkitem in de wachtrij plaatsen voordat het vorige werkitem is voltooid. Werkwachtrijen zijn echter niet gegarandeerd om werkitems te serialiseren. U herinnert zich nog dat een werkwachtrij meer dan één thread kan gebruiken om werkitems te verzenden. In een multithreaded omgeving kan een werkitem worden aangeroepen voordat de vorige is voltooid. Werkitems kunnen zelfs buiten de volgorde worden aangeroepen als er een contextswitch plaatsvindt vlak voordat de callback wordt aangeroepen.
Daarom is het de verantwoordelijkheid van het object om bewerkingen op zichzelf te serialiseren, indien nodig. Met andere woorden, als voor een object bewerking A voltooid moet zijn voordat bewerking B kan beginnen, dan mag het object pas een werkitem in de wachtrij plaatsen voor B nadat bewerking A is voltooid. Een object kan aan deze vereiste voldoen door een eigen wachtrij met bewerkingen in behandeling te hebben. Wanneer een asynchrone methode wordt aangeroepen voor het object, plaatst het object de aanvraag in een eigen wachtrij. Wanneer elke asynchrone bewerking is voltooid, haalt het object de volgende aanvraag uit de wachtrij op. In de MPEG1Source Sample ziet u een voorbeeld van het implementeren van een dergelijke wachtrij.
Een enkele methode kan betrekking hebben op verschillende asynchrone bewerkingen, met name wanneer I/O-aanroepen worden gebruikt. Wanneer u asynchrone methoden implementeert, moet u zorgvuldig nadenken over serialisatievereisten. Is het bijvoorbeeld geldig voor het object om een nieuwe bewerking te starten terwijl een eerdere I/O-aanvraag nog in behandeling is? Als de nieuwe bewerking de interne status van het object wijzigt, wat gebeurt er wanneer een eerdere I/O-aanvraag is voltooid en gegevens retourneert die nu mogelijk verouderd zijn? Een goed statusdiagram kan helpen bij het identificeren van de geldige statusovergangen.
Overwegingen voor thread-overschrijdende en proces-overschrijdende situaties
Werkwachtrijen maken geen gebruik van COM-marshaling voor het verwerken van interface-aanwijzers over thread-grenzen. Dus zelfs als een object is geregistreerd als appartement-threaded of de toepassingsthread een sta (single threaded apartment) heeft ingevoerd, IMFAsyncCallback callbacks worden aangeroepen vanuit een andere thread. In elk geval moeten alle Media Foundation-pijplijnonderdelen het threadingmodel 'Beide' gebruiken.
Sommige interfaces in Media Foundation definiëren externe versies van sommige asynchrone methoden. Wanneer een van deze methoden wordt aangeroepen over procesgrenzen, roept de Media Foundation-proxy/stub-DLL de externe versie van de methode aan, waarmee aangepaste marshaling van de methodeparameters wordt uitgevoerd. In het externe proces vertaalt de stub de aanroep terug naar de lokale methode op het object. Dit proces is transparant voor zowel de toepassing als het externe object. Deze aangepaste marshalingmethoden worden voornamelijk geleverd voor objecten die in het beveiligde mediapad (PMP) worden geladen. Voor meer informatie over het PMP, zie Beveiligde Media Pad.
Verwante onderwerpen