Schreiben einer asynchronen Methode

In diesem Thema wird beschrieben, wie Sie eine asynchrone Methode in Microsoft Media Foundation implementieren.

Asynchrone Methoden sind in der Media Foundation-Pipeline allgegenwärtig. Asynchrone Methoden erleichtern die Verteilung der Arbeit auf mehrere Threads. Es ist besonders wichtig, E/A asynchron auszuführen, damit das Lesen aus einer Datei oder einem Netzwerk den Rest der Pipeline nicht blockiert.

Wenn Sie eine Medienquelle oder Mediensenke schreiben, ist es wichtig, asynchrone Vorgänge ordnungsgemäß zu verarbeiten, da sich die Leistung Ihrer Komponente auf die gesamte Pipeline auswirkt.

Hinweis

Media Foundation-Transformationen (MFTs) verwenden standardmäßig synchrone Methoden.

 

Arbeitswarteschlangen für asynchrone Vorgänge

In Media Foundation besteht eine enge Beziehung zwischen asynchronen Rückrufmethoden und Arbeitswarteschlangen. Eine Arbeitswarteschlange ist eine Abstraktion zum Verschieben von Arbeit vom Aufruferthread in einen Workerthread. Gehen Sie wie folgt vor, um Arbeit an einer Arbeitswarteschlange auszuführen:

  1. Implementieren Sie die IMFAsyncCallback-Schnittstelle .

  2. Rufen Sie MFCreateAsyncResult auf, um ein Ergebnisobjekt zu erstellen. Das Ergebnisobjekt macht das IMFAsyncResult verfügbar. Das Ergebnisobjekt enthält drei Zeiger:

    • Ein Zeiger auf die IMFAsyncCallback-Schnittstelle des Aufrufers.
    • Ein optionaler Zeiger auf ein Zustandsobjekt. Wenn angegeben, muss das State-Objekt IUnknown implementieren.
    • Ein optionaler Zeiger auf ein privates Objekt. Falls angegeben, muss dieses Objekt auch IUnknown implementieren.

    Die letzten beiden Zeiger können NULL sein. Andernfalls können Sie sie verwenden, um Informationen zum asynchronen Vorgang zu enthalten.

  3. Rufen Sie MFPutWorkItemEx auf, um die Arbeitsaufgabe in die Warteschlange zu stellen.

  4. Der Arbeitswarteschlangenthread ruft Ihre IMFAsyncCallback::Invoke-Methode auf.

  5. Führen Sie die Arbeit in Ihrer Invoke-Methode aus. Der pAsyncResult-Parameter dieser Methode ist der IMFAsyncResult-Zeiger aus Schritt 2. Verwenden Sie diesen Zeiger, um das Zustandsobjekt und das private Objekt abzurufen:

Alternativ können Sie die Schritte 2 und 3 kombinieren, indem Sie die MFPutWorkItem-Funktion aufrufen. Intern ruft diese Funktion MFCreateAsyncResult auf, um das Ergebnisobjekt zu erstellen.

Das folgende Diagramm zeigt die Beziehungen zwischen dem Aufrufer, dem Ergebnisobjekt, dem Zustandsobjekt und dem privaten Objekt.

Diagramm eines asynchronen Ergebnisobjekts

Das folgende Sequenzdiagramm zeigt, wie ein Objekt ein Arbeitselement in die Warteschlange stellt. Wenn der Arbeitswarteschlangenthread Invoke aufruft, führt das -Objekt den asynchronen Vorgang für diesen Thread aus.

Diagramm, das zeigt, wie ein Objekt ein Arbeitselement in die Warteschlange stellt

Es ist wichtig zu beachten, dass Invoke von einem Thread aufgerufen wird, der sich im Besitz der Arbeitswarteschlange befindet. Ihre Implementierung von Invoke muss threadsicher sein. Wenn Sie die Plattformarbeitswarteschlange (MFASYNC_CALLBACK_QUEUE_STANDARD) verwenden, ist es außerdem wichtig, dass Sie den Thread niemals blockieren, da dies die Verarbeitung der Daten für die gesamte Media Foundation-Pipeline blockieren kann. Wenn Sie einen Vorgang ausführen müssen, der blockiert oder lange dauert, verwenden Sie eine private Arbeitswarteschlange. Rufen Sie MFAllocateWorkQueue auf, um eine private Arbeitswarteschlange zu erstellen. Jede Pipelinekomponente, die E/A-Vorgänge ausführt, sollte das Blockieren von E/A-Aufrufen aus demselben Grund vermeiden. Die IMFByteStream-Schnittstelle bietet eine nützliche Abstraktion für asynchrone Datei-E/A.

Implementieren von Begin.../End... Muster

Wie unter Aufrufen asynchroner Methoden beschrieben, verwenden asynchrone Methoden in Media Foundation häufig begin.../Ende.... Muster. In diesem Muster verwendet ein asynchroner Vorgang zwei Methoden mit Signaturen ähnlich der folgenden:

// 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);

Damit die Methode wirklich asynchron ist, muss die Implementierung von BeginX die eigentliche Arbeit in einem anderen Thread ausführen. Hier kommen Arbeitswarteschlangen ins Spiel. In den folgenden Schritten ist der Aufrufer der Code, der BeginX und EndX aufruft. Dies kann eine Anwendung oder die Media Foundation-Pipeline sein. Die Komponente ist der Code, der BeginX und EndX implementiert.

  1. Der Aufrufer ruft Begin... auf und übergibt einen Zeiger auf die IMFAsyncCallback-Schnittstelle des Aufrufers.
  2. Die Komponente erstellt ein neues asynchrones Ergebnisobjekt. Dieses Objekt speichert die Rückrufschnittstelle und das Zustandsobjekt des Aufrufers. In der Regel werden auch alle privaten Zustandsinformationen gespeichert, die die Komponente zum Abschließen des Vorgangs benötigt. Das Ergebnisobjekt aus diesem Schritt wird im nächsten Diagramm als "Ergebnis 1" bezeichnet.
  3. Die Komponente erstellt ein zweites Ergebnisobjekt. Dieses Ergebnisobjekt speichert zwei Zeiger: Das erste Ergebnisobjekt und die Rückrufschnittstelle des Aufgerufenen. Dieses Ergebnisobjekt wird im nächsten Diagramm mit "Ergebnis 2" bezeichnet.
  4. Die Komponente ruft MFPutWorkItemEx auf, um ein neues Arbeitselement in die Warteschlange zu stellen.
  5. In der Invoke-Methode übernimmt die Komponente die asynchrone Arbeit.
  6. Die Komponente ruft MFInvokeCallback auf, um die Rückrufmethode des Aufrufers aufzurufen.
  7. Der Aufrufer ruft die EndX-Methode auf.

Diagramm, das zeigt, wie ein Objekt das Anfang/Ende-Muster implementiert

Beispiel für eine asynchrone Methode

Um diese Diskussion zu veranschaulichen, verwenden wir ein erfundenes Beispiel. Betrachten Sie eine asynchrone Methode zum Berechnen einer Quadratwurzel:

    HRESULT BeginSquareRoot(double x, IMFAsyncCallback *pCB, IUnknown *pState);
    HRESULT EndSquareRoot(IMFAsyncResult *pResult, double *pVal);

Der x-Parameter von BeginSquareRoot ist der Wert, dessen Quadratwurzel berechnet wird. Die Quadratwurzel wird im pVal-Parameter von EndSquareRootzurückgegeben.

Dies ist die Deklaration einer Klasse, die diese beiden Methoden implementiert:

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

Die SqrRoot -Klasse implementiert IMFAsyncCallback , sodass der Quadratstammvorgang in eine Arbeitswarteschlange eingefügt werden kann. Die DoCalculateSquareRoot -Methode ist die private Klassenmethode, die die Quadratwurzel berechnet. Diese Methode wird aus dem Arbeitswarteschlangenthread aufgerufen.

Zunächst benötigen wir eine Möglichkeit, den Wert von x zu speichern, damit er abgerufen werden kann, wenn der Arbeitswarteschlangenthread aufruft SqrRoot::Invoke. Hier sehen Sie eine einfache Klasse, die die Informationen speichert:

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

Diese Klasse implementiert IUnknown , sodass sie in einem Ergebnisobjekt gespeichert werden kann.

Der folgende Code implementiert die BeginSquareRoot -Methode:

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

Im Code werden folgende Schritte ausgeführt:

  1. Erstellt eine neue instance der AsyncOp -Klasse, die den Wert von x enthält.
  2. Ruft MFCreateAsyncResult auf, um ein Ergebnisobjekt zu erstellen. Dieses Objekt enthält mehrere Zeiger:
    • Ein Zeiger auf die IMFAsyncCallback-Schnittstelle des Aufrufers.
    • Ein Zeiger auf das Zustandsobjekt des Aufrufers (pState).
    • Ein Zeiger auf das AsyncOp-Objekt.
  3. Ruft MFPutWorkItem auf, um ein neues Arbeitselement in die Warteschlange zu stellen. Dieser Aufruf erstellt implizit ein äußeres Ergebnisobjekt, das die folgenden Zeiger enthält:

Der folgende Code implementiert die SqrRoot::Invoke -Methode:

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

Diese Methode ruft das innere Ergebnisobjekt und das AsyncOp -Objekt ab. Anschließend wird das AsyncOp -Objekt an DoCalculateSquareRootübergeben. Schließlich wird IMFAsyncResult::SetStatus aufgerufen, um den status Code festzulegen, und MFInvokeCallback, um die Rückrufmethode des Aufrufers aufzurufen.

Die DoCalculateSquareRoot -Methode führt genau das aus, was Sie erwarten würden:

HRESULT SqrRoot::DoCalculateSquareRoot(AsyncOp *pOp)
{
    pOp->m_value = sqrt(pOp->m_value);

    return S_OK;
}

Wenn die Rückrufmethode des Aufrufers aufgerufen wird, liegt es in der Verantwortung des Aufrufers, die End...- Methode aufzurufen, EndSquareRootin diesem Fall . Der EndSquareRoot Aufrufer ruft das Ergebnis des asynchronen Vorgangs ab, bei dem es sich in diesem Beispiel um die berechnete Quadratwurzel handelt. Diese Informationen werden im Ergebnisobjekt gespeichert:

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

Vorgangswarteschlangen

Bisher wurde stillschweigend davon ausgegangen, dass ein asynchroner Vorgang jederzeit durchgeführt werden kann, unabhängig vom aktuellen Zustand des Objekts. Berücksichtigen Sie beispielsweise, was passiert, wenn eine Anwendung aufruft BeginSquareRoot , während ein früherer Aufruf derselben Methode noch aussteht. Die SqrRoot -Klasse kann das neue Arbeitselement in die Warteschlange stellen, bevor das vorherige Arbeitselement abgeschlossen ist. Es ist jedoch nicht garantiert, dass Arbeitselemente von Arbeitswarteschlangen serialisiert werden. Denken Sie daran, dass eine Arbeitswarteschlange mehr als einen Thread zum Senden von Arbeitselementen verwenden kann. In einer Multithreadumgebung kann ein Arbeitselement aufgerufen werden, bevor das vorherige abgeschlossen wurde. Arbeitselemente können sogar in der richtigen Reihenfolge aufgerufen werden, wenn ein Kontextwechsel unmittelbar vor dem Aufruf des Rückrufs auftritt.

Aus diesem Grund liegt es in der Verantwortung des Objekts, Vorgänge für sich selbst zu serialisieren, falls erforderlich. Anders ausgedrückt: Wenn für das Objekt Vorgang A abgeschlossen werden muss, bevor Vorgang B gestartet werden kann, darf das Objekt ein Arbeitselement für B erst in die Warteschlange stellen, wenn Vorgang A abgeschlossen ist. Ein Objekt kann diese Anforderung erfüllen, indem es über eine eigene Warteschlange mit ausstehenden Vorgängen verfügt. Wenn eine asynchrone Methode für das -Objekt aufgerufen wird, platziert das -Objekt die Anforderung in einer eigenen Warteschlange. Wenn jeder asynchrone Vorgang abgeschlossen ist, ruft das Objekt die nächste Anforderung aus der Warteschlange ab. Das MPEG1Source-Beispiel zeigt ein Beispiel für die Implementierung einer solchen Warteschlange.

Eine einzelne Methode kann mehrere asynchrone Vorgänge umfassen, insbesondere wenn E/A-Aufrufe verwendet werden. Wenn Sie asynchrone Methoden implementieren, sollten Sie die Serialisierungsanforderungen sorgfältig berücksichtigen. Ist es beispielsweise gültig, dass das Objekt einen neuen Vorgang startet, während eine vorherige E/A-Anforderung noch aussteht? Was geschieht, wenn der neue Vorgang den internen Zustand des Objekts ändert, wenn eine vorherige E/A-Anforderung abgeschlossen ist und Daten zurückgibt, die jetzt möglicherweise veraltet sind? Ein gutes Zustandsdiagramm kann dabei helfen, die gültigen Zustandsübergänge zu identifizieren.

Überlegungen zu Thread- und Prozessübergreifend

Arbeitswarteschlangen verwenden kein COM-Marshalling, um Schnittstellenzeiger über Threadgrenzen hinweg zu marshallen. Selbst wenn ein Objekt als Apartmentthread registriert ist oder der Anwendungsthread in ein Singlethread-Apartment (STA) eingetreten ist, werden IMFAsyncCallback-Rückrufe aus einem anderen Thread aufgerufen. In jedem Fall sollten alle Media Foundation-Pipelinekomponenten das Threadingmodell "Beide" verwenden.

Einige Schnittstellen in Media Foundation definieren Remoteversionen einiger asynchroner Methoden. Wenn eine dieser Methoden über Prozessgrenzen hinweg aufgerufen wird, ruft die Media Foundation-Proxy-/Stub-DLL die Remoteversion der -Methode auf, die benutzerdefiniertes Marshalling der Methodenparameter ausführt. Im Remoteprozess übersetzt der Stub den Aufruf zurück in die lokale Methode für das Objekt. Dieser Prozess ist sowohl für die Anwendung als auch für das Remoteobjekt transparent. Diese benutzerdefinierten Marshallingmethoden werden in erster Linie für Objekte bereitgestellt, die im geschützten Medienpfad (Protected Media Path, PMP) geladen werden. Weitere Informationen zum PMP finden Sie unter Pfad für geschützte Medien.

Asynchrone Rückrufmethoden

Arbeitswarteschlangen