Scrittura di un metodo asincrono
Questo argomento descrive come implementare un metodo asincrono in Microsoft Media Foundation.
I metodi asincroni sono onnipresenti nella pipeline di Media Foundation. I metodi asincroni semplificano la distribuzione del lavoro tra più thread. È particolarmente importante eseguire operazioni di I/O in modo asincrono, in modo che la lettura da un file o da una rete non blocchi il resto della pipeline.
Se tu scrivi un'origine multimediale o un sink multimediale, è fondamentale gestire correttamente le operazioni asincrone, dal momento che le prestazioni del tuo componente hanno un impatto sull'intera pipeline.
Nota
Per impostazione predefinita, le trasformazioni di Media Foundation usano metodi sincroni.
Code di lavoro per operazioni asincrone
In Media Foundation esiste una relazione stretta tra i metodi di callback asincroni e le code di lavoro . Una coda di lavoro è un'astrazione per trasferire il lavoro dal thread del chiamante a un thread di lavoro. Per eseguire operazioni su una coda di lavoro, eseguire le operazioni seguenti:
Implementare l'interfaccia IMFAsyncCallback.
Chiamare MFCreateAsyncResult per creare un oggetto risultato . L'oggetto risultato espone il IMFAsyncResult. L'oggetto risultato contiene tre puntatori:
- Puntatore all'interfaccia IMFAsyncCallback del chiamante.
- Un puntatore facoltativo a un oggetto di stato. Se specificato, l'oggetto stato deve implementare IUnknown.
- Puntatore facoltativo a un oggetto privato. Se specificato, questo oggetto deve implementare anche IUnknown.
Gli ultimi due puntatori possono essere NULL. Altrimenti, usali per contenere informazioni sull'operazione asincrona.
Chiamare MFPutWorkItemEx per mettere in coda l'elemento di lavoro.
Il thread della coda di lavoro chiama il metodo IMFAsyncCallback::Invoke.
Esegui il lavoro all'interno del metodo Invoke. Il parametro pAsyncResult di questo metodo è il puntatore IMFAsyncResult del passaggio 2. Usare questo puntatore per ottenere l'oggetto di stato e l'oggetto privato:
- Per ottenere l'oggetto stato, chiamare IMFAsyncResult::GetState.
- Per ottenere l'oggetto privato, chiamare IMFAsyncResult::GetObject.
In alternativa, è possibile combinare i passaggi 2 e 3 chiamando la funzioneMFPutWorkItem. Internamente, questa funzione chiama MFCreateAsyncResult per creare l'oggetto risultato.
Il diagramma seguente illustra le relazioni tra il chiamante, l'oggetto risultato, l'oggetto stato e l'oggetto privato.
Il diagramma di sequenza seguente mostra come un oggetto accoda un elemento di lavoro. Quando il thread di coda di lavoro invoca , l'oggetto esegue l'operazione asincrona su quel thread.
È importante ricordare che Invoke viene chiamato da un thread che è di proprietà della coda di lavoro. L'implementazione di Invoke deve essere thread-safe. Inoltre, se si usa la coda di lavoro della piattaforma (MFASYNC_CALLBACK_QUEUE_STANDARD), è fondamentale non bloccare mai il thread, perché ciò può bloccare l'intera pipeline di Media Foundation dall'elaborazione dei dati. Se è necessario eseguire un'operazione che bloccherà o richiederà molto tempo per essere completata, usare una coda di lavoro privata. Per creare una coda di lavoro privata, chiamare MFAllocateWorkQueue. Qualsiasi componente della pipeline che esegue operazioni di I/O deve evitare di bloccare le chiamate di I/O per lo stesso motivo. L'interfaccia IMFByteStream fornisce un'astrazione utile per l'I/O di file asincrono.
Implementazione di Begin.../End... Modello
Come descritto in Chiamata di metodi asincroni, i metodi asincroni in Media Foundation usano spesso il modello Begin.../End..... In questo modello, un'operazione asincrona usa due metodi con firme simili alle seguenti:
// 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);
Per rendere il metodo veramente asincrono, l'implementazione di BeginX deve eseguire il lavoro effettivo su un altro thread. È qui che le code di lavoro entrano in gioco. Nei passaggi seguenti, il chiamante è il codice che chiama BeginX e EndX. Potrebbe trattarsi di un'applicazione o della pipeline di Media Foundation. Il componente è il codice che implementa BeginX e EndX.
- Il chiamante chiama Begin..., passando un puntatore all'interfaccia IMFAsyncCall back del chiamante.
- Il componente crea un nuovo oggetto risultato asincrono. Questo oggetto archivia l'interfaccia di callback e l'oggetto stato del chiamante. In genere, archivia anche tutte le informazioni sullo stato privato necessarie al componente per completare l'operazione. L'oggetto risultato di questo passaggio viene etichettato come "Risultato 1" nel diagramma successivo.
- Il componente crea un secondo oggetto risultato. Questo oggetto di risultato conserva due puntatori: il primo oggetto di risultato e l'interfaccia di callback del destinatario della chiamata. Questo oggetto risultato viene etichettato come "Risultato 2" nel diagramma successivo.
- Il componente chiama MFPutWorkItemEx per accodare un nuovo elemento di lavoro.
- Nel metodo Invoke il componente esegue il lavoro asincrono.
- Il componente chiama MFInvokeCallback per richiamare il metodo di callback del chiamante.
- Il chiamante chiama il metodo EndX.
Esempio di metodo asincrono
Per illustrare questa discussione, utilizzeremo un esempio fittizio. Si consideri un metodo asincrono per il calcolo di una radice quadrata:
HRESULT BeginSquareRoot(double x, IMFAsyncCallback *pCB, IUnknown *pState);
HRESULT EndSquareRoot(IMFAsyncResult *pResult, double *pVal);
Il parametro x di BeginSquareRoot
è il valore di cui verrà calcolata la radice quadrata. La radice quadrata viene restituita nel parametro pVal di EndSquareRoot
.
Ecco la dichiarazione di una classe che implementa questi due metodi:
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);
};
La classe SqrRoot
implementa IMFAsyncCallback in modo che possa inserire l'operazione radice quadrata in una coda di lavoro. Il metodo DoCalculateSquareRoot
è il metodo della classe privata che calcola la radice quadrata. Questo metodo sarà invocato dal thread della coda di lavoro.
Prima di tutto, è necessario un modo per archiviare il valore di x, in modo che possa essere recuperato quando il thread della coda di lavoro chiama SqrRoot::Invoke
. Ecco una classe semplice che archivia le informazioni:
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;
}
};
Questa classe implementa IUnknown in modo che possa essere archiviata in un oggetto risultato.
Il codice seguente implementa il metodo 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;
}
Questo codice esegue le operazioni seguenti:
- Crea una nuova istanza della classe
AsyncOp
per contenere il valore di x. - Chiama MFCreateAsyncResult per creare un oggetto risultato. Questo oggetto contiene diversi puntatori:
- Puntatore all'interfaccia IMFAsyncCallback del chiamante.
- Puntatore allo stato dell'oggetto del chiamante (pState).
- Puntatore all'oggetto
AsyncOp
.
- Chiama MFPutWorkItem per accodare un nuovo elemento di lavoro. Questa chiamata crea in modo implicito un oggetto risultato esterno, che contiene i puntatori seguenti:
- Puntatore all'interfaccia IMFAsyncCallbackdell'oggetto
SqrRoot
. - Puntatore all'oggetto risultato interno del passaggio 2.
- Puntatore all'interfaccia IMFAsyncCallbackdell'oggetto
Il codice seguente implementa il metodo 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;
}
Questo metodo ottiene l'oggetto risultato interno e l'oggetto AsyncOp
. Passa quindi l'oggetto AsyncOp
a DoCalculateSquareRoot
. Infine, chiama IMFAsyncResult::SetStatus per impostare il codice di stato e MFInvokeCallback per richiamare il metodo di callback del chiamante.
Il metodo DoCalculateSquareRoot
esegue esattamente ciò che ci si aspetta:
HRESULT SqrRoot::DoCalculateSquareRoot(AsyncOp *pOp)
{
pOp->m_value = sqrt(pOp->m_value);
return S_OK;
}
Quando viene richiamato il metodo di callback del chiamante, è responsabilità del chiamante chiamare il metodo End..., in questo caso EndSquareRoot
. Il EndSquareRoot
è il modo in cui il chiamante recupera il risultato dell'operazione asincrona, che in questo esempio è la radice quadrata calcolata. Queste informazioni vengono archiviate nell'oggetto risultato:
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;
}
Code di operazioni
Finora, si presuppone che un'operazione asincrona possa essere eseguita in qualsiasi momento, indipendentemente dallo stato corrente dell'oggetto. Si consideri ad esempio cosa accade se un'applicazione chiama BeginSquareRoot
mentre una chiamata precedente allo stesso metodo è ancora in sospeso. La classe SqrRoot
potrebbe accodare il nuovo elemento di lavoro prima del completamento dell'elemento di lavoro precedente. Tuttavia, le code di lavoro non garantiscono la serializzazione degli elementi di lavoro. Tenere presente che una coda di lavoro può usare più di un thread per inviare gli elementi di lavoro. In un ambiente multithreading, è possibile richiamare un elemento di lavoro prima del completamento di quello precedente. Gli elementi di lavoro possono anche essere richiamati fuori ordine, se un cambio di contesto si verifica subito prima che venga richiamato il callback.
Per questo motivo, è responsabilità dell'oggetto serializzare le operazioni su se stesso, se necessario. In altre parole, se l'oggetto richiede il completamento dell'operazione A prima che l'operazione B possa essere avviata, l'oggetto non deve accodare un elemento di lavoro per B fino al completamento dell'operazione A. Un oggetto può soddisfare questo requisito disponendo di una propria coda di operazioni in sospeso. Quando un metodo asincrono viene chiamato sull'oggetto, l'oggetto inserisce la richiesta nella propria coda. Al termine di ogni operazione asincrona, l'oggetto estrae la richiesta successiva dalla coda. Il campione MPEG1Source mostra un esempio di come implementare tale coda.
Un singolo metodo può comportare diverse operazioni asincrone, in particolare quando vengono usate chiamate di I/O. Quando si implementano metodi asincroni, considerare attentamente i requisiti di serializzazione. Ad esempio, è possibile che l'oggetto avvii una nuova operazione mentre una richiesta di I/O precedente è ancora in sospeso? Se la nuova operazione modifica lo stato interno dell'oggetto, cosa accade quando una richiesta di I/O precedente viene completata e restituisce i dati che potrebbero essere obsoleti? Un diagramma di stato valido può essere utile per identificare le transizioni di stato valide.
Considerazioni tra thread e tra processi
Le code di lavoro non usano il marshaling COM per gestire i puntatori di interfaccia attraverso i confini del thread. Pertanto, anche se un oggetto viene registrato come a thread apartment o il thread dell'applicazione ha immesso un apartment a thread singolo (STA), IMFAsyncCallback i callback verranno richiamati da un thread diverso. In ogni caso, tutti i componenti della pipeline di Media Foundation devono usare il modello di threading "Both".
Alcune interfacce in Media Foundation definiscono versioni remote di alcuni metodi asincroni. Quando uno di questi metodi viene chiamato oltre i limiti del processo, la DLL proxy/stub di Media Foundation chiama la versione remota del metodo, che esegue il marshalling personalizzato dei parametri del metodo. Nel processo remoto, lo stub riconverte la chiamata nel metodo locale dell'oggetto. Questo processo è trasparente sia per l'applicazione che per l'oggetto remoto. Questi metodi di marshalling personalizzati vengono forniti principalmente per gli oggetti caricati nel percorso multimediale protetto (PMP). Per altre informazioni sul PMP, vedere Percorso multimediale protetto.
Argomenti correlati