Condividi tramite


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 tra diversi thread. È particolarmente importante eseguire I/O in modo asincrono, in modo che la lettura da un file o una rete non blocchi il resto della pipeline.

Se si scrive un'origine multimediale o un sink multimediale, è fondamentale gestire correttamente le operazioni asincrone, perché le prestazioni del componente hanno un impatto sull'intera pipeline.

Nota

Le trasformazioni di Media Foundation (MFT) usano metodi sincroni per impostazione predefinita.

 

Code di lavoro per operazioni asincrone

In Media Foundation esiste una relazione stretta tra metodi di callback asincroni e code di lavoro. Una coda di lavoro è un'astrazione per lo spostamento del lavoro dal thread del chiamante a un thread di lavoro. Per eseguire operazioni su una coda di lavoro, eseguire le operazioni seguenti:

  1. Implementare l'interfaccia IMFAsyncCallback .

  2. Chiamare MFCreateAsyncResult per creare un oggetto risultato . L'oggetto risultato espone l'OGGETTO FMAsyncResult. L'oggetto risultato contiene tre puntatori:

    • Puntatore all'interfaccia FMAsyncCallback del chiamante.
    • Puntatore facoltativo a un oggetto state. Se specificato, l'oggetto state deve implementare IUnknown.
    • Puntatore facoltativo a un oggetto privato. Se specificato, questo oggetto deve implementare anche IUnknown.

    Gli ultimi due puntatori possono essere NULL. In caso contrario, usarli per contenere informazioni sull'operazione asincrona.

  3. Chiamare MFPutWorkItemEx per accodare l'elemento di lavoro.

  4. Il thread della coda di lavoro chiama il metodo FMAsyncCallback::Invoke .

  5. Eseguire il lavoro all'interno del metodo Invoke . Il parametro pAsyncResult di questo metodo è il puntatore IMFAsyncResult dal passaggio 2. Usare questo puntatore per ottenere l'oggetto state e l'oggetto privato:

In alternativa, è possibile combinare i passaggi 2 e 3 chiamando la funzione MFPutWorkItem . Internamente, questa funzione chiama MFCreateAsyncResult per creare l'oggetto risultato.

Il diagramma seguente illustra le relazioni tra il chiamante, l'oggetto result, l'oggetto state e l'oggetto privato.

diagramma che mostra un oggetto risultato asincrono

Il diagramma della sequenza seguente illustra come un oggetto accoda un elemento di lavoro. Quando il thread della coda di lavoro chiama Invoke, l'oggetto esegue l'operazione asincrona in tale thread.

diagramma che mostra come un oggetto accoda un elemento di lavoro

È importante ricordare che Invoke viene chiamato da un thread 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é può bloccare l'intera pipeline di Media Foundation dall'elaborazione dei dati. Se è necessario eseguire un'operazione che blocca o richiede molto tempo per completare, 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 FMByteStream fornisce un'astrazione utile per i file asincroni di I/O.

Implementazione di Begin.../End... Modello

Come descritto in Chiama metodi asincroni, i metodi asincroni in Media Foundation spesso usano begin.../Fine.... Modello. 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. Questa è la posizione in cui le code di lavoro si trovano nell'immagine. Nei passaggi che seguono, 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.

  1. Il chiamante chiama Begin..., passando un puntatore all'interfaccia FMAsyncCallback del chiamante.
  2. Il componente crea un nuovo oggetto risultato asincrono. Questo oggetto archivia l'interfaccia di callback del chiamante e l'oggetto state. In genere, archivia anche eventuali informazioni sullo stato privato che il componente deve completare l'operazione. L'oggetto risultato di questo passaggio viene etichettato "Result 1" nel diagramma successivo.
  3. Il componente crea un secondo oggetto risultato. Questo oggetto risultato archivia due puntatori: il primo oggetto risultato e l'interfaccia di callback del chiamante. Questo oggetto risultato è etichettato "Result 2" nel diagramma successivo.
  4. Il componente chiama MFPutWorkItemEx per accodare un nuovo elemento di lavoro.
  5. Nel metodo Invoke il componente esegue il funzionamento asincrono.
  6. Il componente chiama MFInvokeCallback per richiamare il metodo di callback del chiamante.
  7. Il chiamante chiama il metodo EndX .

diagramma che mostra come un oggetto implementa il modello begin/end

Esempio di metodo asincrono

Per illustrare questa discussione, si userà un esempio contrived. Prendere in considerazione 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 la cui radice quadrata verrà calcolata. 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 SqrRoot classe implementa IMFAsyncCallback in modo che possa inserire l'operazione radice quadrata in una coda di lavoro. Il DoCalculateSquareRoot metodo è il metodo della classe privata che calcola la radice quadrata. Questo metodo verrà chiamato dal thread della coda di lavoro.

Prima di tutto, è necessario 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 BeginSquareRoot metodo:

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;
}

Il codice esegue le attività seguenti:

  1. Crea una nuova istanza della AsyncOp classe per contenere il valore di x.
  2. Chiama MFCreateAsyncResult per creare un oggetto risultato. Questo oggetto contiene diversi puntatori:
    • Puntatore all'interfaccia FMAsyncCallback del chiamante.
    • Puntatore all'oggetto stato del chiamante (pState).
    • Puntatore all'oggetto AsyncOp.
  3. 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 SqrRootFMAsyncCallback dell'oggetto.
    • Puntatore all'oggetto risultato interno dal passaggio 2.

Il codice seguente implementa il SqrRoot::Invoke metodo:

// 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 FMAsyncResult::SetStatus per impostare il codice di stato e MFInvokeCallback per richiamare il metodo di callback del chiamante.

Il DoCalculateSquareRoot metodo esegue esattamente ciò che si prevede:

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 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

Fino a questo momento, 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 SqrRoot classe potrebbe accodamento del nuovo elemento di lavoro prima che venga eseguito l'elemento di lavoro precedente. Tuttavia, le code di lavoro non sono garantite per serializzare gli 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 in ordine, se si verifica un cambio di contesto 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 dell'avvio dell'operazione B , 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 viene chiamato un metodo asincrono sull'oggetto , l'oggetto inserisce la richiesta nella propria coda. Al termine di ogni operazione asincrona, l'oggetto esegue il pull della richiesta successiva dalla coda. L'esempio MPEG1Source mostra un esempio di come implementare una coda di questo tipo.

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, è valido per l'oggetto avviare 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 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 marshalling COM per effettuare il marshalling dei puntatori di interfaccia attraverso i limiti del thread. Pertanto, anche se un oggetto viene registrato come thread apartment o il thread dell'applicazione ha immesso un apartment a thread singolo (STA), i callback IMFAsyncCallback verranno richiamati da un thread diverso. In ogni caso, tutti i componenti della pipeline 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 attraverso 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 converte nuovamente la chiamata nel metodo locale sull'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 Protected Media Path.For more information about the PMP, see Protected Media Path.

Metodi di callback asincroni

Code di lavoro