Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Toto téma popisuje, jak implementovat asynchronní metodu v Microsoft Media Foundation.
Asynchronní metody jsou v procesu Media Foundation všudypřítomné. Asynchronní metody usnadňují distribuci práce mezi několik vláken. Je zvlášť důležité provádět vstupně-výstupní operace asynchronně, takže čtení ze souboru nebo sítě neblokuje zbytek kanálu.
Pokud píšete zdroj médií nebo příjemce médií, je důležité správně zpracovat asynchronní operace, protože výkon vaší komponenty má vliv na celé potrubí.
Poznámka
Transformace Media Foundation (MFT) ve výchozím nastavení používají synchronní metody.
Pracovní fronty pro asynchronní operace
V Media Foundation existuje blízký vztah mezi asynchronní metodou zpětného volání a pracovní frontou. Pracovní fronta je abstrakce pro přesun práce z vlákna volajícího do pracovního vlákna. Pokud chcete provádět práci v pracovní frontě, postupujte takto:
Implementujte rozhraní MMFAsyncCallback.
Zavolejte MFCreateAsyncResult pro vytvoření objektu výsledku . Výsledný objekt odhaluje IMFAsyncResult. Výsledný objekt obsahuje tři ukazatele:
- Ukazatel na rozhraní IMFAsyncCallback volajícího.
- Volitelný ukazatel na objekt stavu. Je-li zadán, stavový objekt musí implementovat IUnknown.
- Volitelný ukazatel na soukromý objekt. Pokud je zadán, tento objekt musí také implementovat IUnknown.
Poslední dva ukazatele mohou být null. V opačném případě je použijte k uložení informací o asynchronní operaci.
Volání MFPutWorkItemEx do fronty na pracovní položku.
Vlákno pracovní fronty volá vaši IMFAsyncCallback::Invoke metodu.
Proveďte práci uvnitř metody Invoke. Parametr pAsyncResult této metody je ukazatel MMFAsyncResult z kroku 2. Tento ukazatel použijte k získání objektu stavu a privátního objektu:
- Chcete-li získat objekt stavu, zavolejte IMFAsyncResult::GetState.
- Chcete-li získat privátní objekt, zavolejte MMFAsyncResult::GetObject.
Alternativně můžete zkombinovat kroky 2 a 3 voláním funkce MFPutWorkItem. Interně tato funkce volá MFCreateAsyncResult k vytvoření objektu výsledku.
Následující diagram znázorňuje vztahy mezi volajícím, objektem výsledku, stavem a privátním objektem.
Následující sekvenční diagram znázorňuje, jak objekt zařadí pracovní položku. Když vlákno pracovní fronty vyvolá , objekt provede asynchronní operaci na tomto vlákně.
Je důležité si uvědomit, Vyvolání se volá z vlákna, které vlastní pracovní fronta. Vaše implementace Invoke musí být bezpečná pro přístup z více vláken. Pokud navíc používáte pracovní frontu platformy (MFASYNC_CALLBACK_QUEUE_STANDARD), je zásadní, abyste vlákno nikdy neblokovali, protože to může zablokovat celý pipeline Media Foundation při zpracování dat. Pokud potřebujete provést operaci, která způsobí blokování nebo bude trvat dlouho, použijte soukromou pracovní frontu. Chcete-li vytvořit privátní pracovní frontu, zavolejte MFAllocateWorkQueue. Jakákoli komponenta kanálu, která provádí vstupně-výstupní operace, by se měla vyhnout blokování vstupně-výstupních volání z stejného důvodu. Rozhraní MMFByteStream poskytuje užitečnou abstrakci pro asynchronní vstupně-výstupní operace souborů.
Implementace vzoru Začátek.../Konec...
Jak je popsáno v volání asynchronních metod, v Media Foundation se asynchronní metody často používají ve vzoru Begin.../End..... V tomto vzoru asynchronní operace používá dvě metody s podpisy podobné následujícímu:
// 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);
Aby byla metoda skutečně asynchronní, musí implementace BeginX provést skutečnou práci v jiném vlákně. Tady přicházejí do obrázku pracovní fronty. V následujících krocích je volající kód, který volá BeginX a EndX. Může se jednat o aplikaci nebo zpracovatelský řetězec Media Foundation. Komponenta je kód, který implementuje BeginX a EndX.
- Volající volá Begin...a předává ukazatel na rozhraní IMFAsyncCallback volajícího.
- Komponenta vytvoří nový objekt asynchronního výsledku. Tento objekt ukládá rozhraní zpětného volání volajícího a objekt stavu. Obvykle také ukládá všechny informace o privátním stavu, které komponenta potřebuje k dokončení operace. Objekt výsledku z tohoto kroku je v dalším diagramu označený jako Výsledek 1.
- Komponenta vytvoří druhý výsledný objekt. Tento objekt výsledku ukládá dva ukazatele: první objekt výsledku a rozhraní zpětného volání volaného. Tento objekt výsledku je v dalším diagramu označený jako Výsledek 2.
- Komponenta volá MFPutWorkItemEx, aby přidala novou pracovní položku do fronty.
- V metodě Invoke komponenta vykonává asynchronní práci.
- Komponenta volá MFInvokeCallback, aby vyvolala metodu zpětného volání volajícího.
- Volající volá metodu EndX.
Příklad asynchronní metody
K ilustraci této diskuze použijeme vykonstruovaný příklad. Zvažte asynchronní metodu pro výpočet druhou odmocninu:
HRESULT BeginSquareRoot(double x, IMFAsyncCallback *pCB, IUnknown *pState);
HRESULT EndSquareRoot(IMFAsyncResult *pResult, double *pVal);
Parametr xBeginSquareRoot je hodnota, jejíž druhou odmocninu se vypočítá. Druhá odmocnina je vrácena v parametru pValEndSquareRoot.
Toto je deklarace třídy, která implementuje tyto dvě metody:
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);
};
Třída SqrRoot implementuje IMFAsyncCallback, aby mohla umístit operaci druhé odmocniny do pracovní fronty. Metoda DoCalculateSquareRoot je soukromá metoda třídy, která vypočítá druhou odmocninu. Tato metoda bude zavolána z vlákna pracovní fronty.
Nejprve potřebujeme najít způsob, jak uložit hodnotu x, aby ji bylo možné načíst, když vlákno pracovní fronty zavolá SqrRoot::Invoke. Tady je jednoduchá třída, která ukládá informace:
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;
}
};
Tato třída implementuje IUnknown, aby bylo možné jej uložit do objektu výsledku.
Následující kód implementuje metodu BeginSquareRoot:
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;
}
Tento kód provede následující:
- Vytvoří novou instanci třídy
AsyncOp, která bude obsahovat hodnotu x. - Volání MFCreateAsyncResult k vytvoření objektu výsledku. Tento objekt obsahuje několik ukazatelů:
- Ukazatel na rozhraní IMFAsyncCallback volajícího.
- Ukazatel na objekt stavu volajícího (pState).
- Ukazatel na objekt
AsyncOp.
- Volá MFPutWorkItem k zařazení nové pracovní položky do fronty. Toto volání implicitně vytvoří vnější objekt výsledku, který obsahuje následující ukazatele:
- Ukazatel na rozhraní
SqrRootobjektu IMFAsyncCallback. - Ukazatel na objekt vnitřního výsledku z kroku 2.
- Ukazatel na rozhraní
Následující kód implementuje metodu SqrRoot::Invoke:
// 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;
}
Tato metoda získá vnitřní objekt výsledku a objekt AsyncOp. Pak předá objekt AsyncOpDoCalculateSquareRoot. Nakonec volá IMFAsyncResult::SetStatus pro nastavení stavového kódu a MFInvokeCallback pro vyvolání metody zpětného volání volajícího.
Metoda DoCalculateSquareRoot přesně dělá to, co byste očekávali:
HRESULT SqrRoot::DoCalculateSquareRoot(AsyncOp *pOp)
{
pOp->m_value = sqrt(pOp->m_value);
return S_OK;
}
Když je zavolána metoda zpětného volání volajícího, je na volajícím, aby zavolal metodu End... – v tomto případě EndSquareRoot.
EndSquareRoot je způsob, jakým volající načte výsledek asynchronní operace, což je v tomto příkladu vypočítaná druhá odmocnina. Tyto informace jsou uloženy ve výsledném objektu:
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;
}
Operační fronty
Zatím se předpokládá, že asynchronní operace může být provedena kdykoli bez ohledu na aktuální stav objektu. Zvažte například, co se stane, když aplikace volá BeginSquareRoot zatímco dřívější volání stejné metody stále čeká na vyřízení. Třída SqrRoot může zařadit novou pracovní položku do fronty před dokončením předchozí pracovní položky. Pracovní fronty však nejsou zaručeny pro serializaci pracovních položek. Vzpomeňte si, že pracovní fronta může k odesílání pracovních položek použít více než jedno vlákno. V prostředí s více vlákny může být pracovní položka vyvolána před dokončením předchozího. Pracovní položky lze dokonce vyvolat mimo pořadí, pokud dojde k přepnutí kontextu těsně před vyvoláním zpětného volání.
Z tohoto důvodu je odpovědností objektu serializovat operace na sobě, pokud je to potřeba. Jinými slovy, pokud objekt vyžaduje operaci A dokončit před zahájením operace B, nesmí objekt zařaďovat pracovní položku do fronty pro B, dokud se operace A nedokončí. Objekt může tento požadavek splnit tak, že má vlastní frontu čekajících operací. Pokud je v objektu volána asynchronní metoda, objekt umístí požadavek do vlastní fronty. Při dokončení každé asynchronní operace objekt stáhne další požadavek z fronty. Ukázka MPEG1Source ukazuje příklad implementace takové fronty.
Jedna metoda může zahrnovat několik asynchronních operací, zejména při použití vstupně-výstupních volání. Při implementaci asynchronních metod pečlivě zvažte požadavky serializace. Je například přípustné, aby objekt spustil novou operaci, zatímco předchozí vstupně-výstupní požadavek je stále nevyřízený? Pokud nová operace změní vnitřní stav objektu, co se stane, když se dokončí předchozí vstupně-výstupní požadavek a vrátí data, která by teď mohla být zastaralá? Dobrý stavový diagram vám pomůže identifikovat platné přechody stavu.
Úvahy týkající se křížových vláken a křížových procesů
Pracovní fronty nepoužívají marshaling modelu COM k přenosu ukazatelů rozhraní mezi vlákny. Proto i v případě, že je objekt zaregistrovaný jako apartment-threaded nebo že vlákno aplikace vstoupilo do jednovláknového apartmánu (STA), IMFAsyncCallback zpětná volání budou vyvolána z jiného vlákna. V každém případě by všechny komponenty kanálu Media Foundation měly používat model vláken "Both."
Některá rozhraní v Media Foundation definují vzdálené verze některých asynchronních metod. Pokud je jedna z těchto metod volána přes hranice procesu, proxy/stub DLL Media Foundation volá vzdálenou verzi metody, která provádí vlastní marshaling parametrů metody. Ve vzdáleném procesu zástupný kód překládá volání zpět do místní metody objektu. Tento proces je pro aplikaci i pro vzdálený objekt transparentní. Tyto vlastní metody maršálování jsou poskytovány především pro objekty, které jsou načteny v chráněné mediální cestě (PMP). Další informace o PMP naleznete v tématu Chráněná cesta média.
Související témata