Udostępnij przez


Pisanie metody asynchronicznej

W tym temacie opisano sposób implementowania metody asynchronicznej w programie Microsoft Media Foundation.

Metody asynchroniczne są wszechobecne w przepływie pracy Media Foundation. Metody asynchroniczne ułatwiają dystrybucję pracy między kilkoma wątkami. Szczególnie ważne jest, aby asynchronicznie wykonywać operacje we/wy, dzięki czemu odczyt z pliku lub sieci nie blokuje reszty potoku.

Jeśli piszesz źródło lub ujście mediów, kluczowe znaczenie ma prawidłowe obsługiwanie operacji asynchronicznych, ponieważ wydajność składnika ma wpływ na cały przepływ.

Notatka

Przekształcenia programu Media Foundation (MFTs) domyślnie używają metod synchronicznych.

 

Kolejki robocze dla operacji asynchronicznych

W programie Media Foundation istnieje ścisła relacja między metodami asynchronicznego wywołania zwrotnego i kolejkami pracy . Kolejka pracy to abstrakcja przenoszenia pracy z wątku wywołującego do wątku roboczego. Aby wykonać pracę w kolejce pracy, wykonaj następujące czynności:

  1. Zaimplementuj interfejs IMFAsyncCallback.

  2. Wywołaj MFCreateAsyncResult, aby utworzyć wynik obiektu . Obiekt result uwidacznia IMFAsyncResult. Obiekt wynikowy zawiera trzy wskaźniki:

    • Wskaźnik do interfejsu IMFAsyncCallback wywołującego.
    • Opcjonalny wskaźnik do obiektu stanu. Jeśli zostanie określony, obiekt stanu musi zaimplementować IUnknown.
    • Opcjonalny wskaźnik do obiektu prywatnego. Jeśli zostanie określony, ten obiekt musi również zaimplementować IUnknown.

    Ostatnie dwa wskaźniki mogą być null. W przeciwnym razie użyj ich do przechowywania informacji o operacji asynchronicznej.

  3. Wywołaj MFPutWorkItemEx do kolejki jako element roboczy.

  4. Wątek kolejki roboczej wywołuje funkcję IMFAsyncCallback::Invoke.

  5. Wykonaj pracę w metodzie Invoke. Parametr pAsyncResult tej metody to wskaźnik IMFAsyncResult z kroku 2. Użyj tego wskaźnika, aby uzyskać obiekt stanu i obiekt prywatny:

Alternatywnie możesz połączyć kroki 2 i 3, wywołując funkcję MFPutWorkItem. Wewnętrznie ta funkcja wywołuje MFCreateAsyncResult w celu utworzenia obiektu wyniku.

Na poniższym diagramie przedstawiono relacje między obiektem wywołującym, obiektem wynikowym, obiektem stanu i obiektem prywatnym.

diagram przedstawiający obiekt wyniku asynchronicznego

Na poniższym diagramie sekwencji można zobaczyć, jak obiekt umieszcza element roboczy w kolejce. Gdy wątek kolejki roboczej wywołuje Invoke, obiekt wykonuje operację asynchroniczną w tym wątku.

diagram pokazujący, jak obiekt kolejkuje element roboczy

Należy pamiętać, że Invoke jest wywoływane z wątku należącego do kolejki roboczej. Implementacja Invoke musi być bezpieczna dla wątków. Ponadto w przypadku korzystania z kolejki roboczej platformy (MFASYNC_CALLBACK_QUEUE_STANDARD) krytyczne jest, aby nigdy nie blokować wątku, ponieważ może to uniemożliwić przetwarzanie danych przez cały potok programu Media Foundation. Jeśli musisz wykonać operację, która zablokuje lub zajmie dużo czasu, użyj prywatnej kolejki roboczej. Aby utworzyć prywatną kolejkę pracy, wywołaj MFAllocateWorkQueue. Każdy składnik potoku, który wykonuje operacje wejścia/wyjścia, powinien unikać blokowania wywołań wejścia/wyjścia z tych samych powodów. Interfejs IMFByteStream zapewnia przydatną abstrakcję dla asynchronicznych operacji we/wy pliku.

Implementacja wzorca Begin.../End...

Zgodnie z opisem w Wywoływanie metod asynchronicznychmetody asynchroniczne w programie Media Foundation często używają wzorca Begin.../End..... W tym wzorcu operacja asynchroniczna używa dwóch metod z podpisami podobnymi do następujących:

// 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 metoda była naprawdę asynchroniczna, implementacja BeginX musi wykonać rzeczywistą pracę na innym wątku. W tym miejscu kolejki robocze zaczynają odgrywać rolę. W poniższych krokach wywołujący jest kod wywołujący BeginX i EndX. Może to być aplikacja lub szyna Media Foundation. Składnik to kod implementujący BeginX i EndX.

  1. Obiekt wywołujący wywołuje Begin..., przekazując wskaźnik do interfejsu IMFAsyncCallback , należącego do obiektu wywołującego,.
  2. Składnik tworzy nowy asynchroniczny obiekt wyniku. Ten obiekt przechowuje interfejs wywołania zwrotnego wywołującego oraz obiekt stanu. Zazwyczaj przechowuje również wszelkie informacje o stanie prywatnym, które składnik potrzebuje do ukończenia operacji. Obiekt wynikowy z tego kroku ma etykietę "Wynik 1" na następnym diagramie.
  3. Składnik tworzy drugi obiekt wynikowy. Ten obiekt wynikowy przechowuje dwa wskaźniki: pierwszy obiekt wyniku i interfejs wywołania zwrotnego obiektu wywoływanego. Ten obiekt wynikowy ma etykietę "Wynik 2" na następnym diagramie.
  4. Składnik wywołuje MFPutWorkItemEx w celu kolejkowania nowego elementu roboczego.
  5. W metodzie Invoke składnik wykonuje asynchroniczną pracę.
  6. Składnik wywołuje MFInvokeCallback w celu wywołania metody wywołania zwrotnego wywołującego.
  7. Obiekt wywołujący wywołuje metodę EndX.

diagram pokazujący, jak obiekt implementuje wzorzec początku/końca

Przykład metody asynchronicznej

Aby zilustrować tę dyskusję, użyjemy przedstawionego przykładu. Rozważ metodę asynchroniczną przetwarzania pierwiastek kwadratowy:

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

Parametr x z BeginSquareRoot to wartość, której pierwiastek kwadratowy zostanie obliczony. Pierwiastek kwadratowy jest zwracany w parametrze pValEndSquareRoot.

Oto deklaracja klasy, która implementuje te dwie 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);
};

Klasa SqrRoot implementuje IMFAsyncCallback, dzięki czemu może umieścić operację pierwiastka kwadratowego w kolejce roboczej. Metoda DoCalculateSquareRoot to prywatna metoda klasy, która oblicza pierwiastek kwadratowy. Ta metoda zostanie wywołana z wątku kolejki roboczej.

Najpierw potrzebujemy sposobu przechowywania wartości x, aby można było ją pobrać, gdy wątek kolejki zadań wywołuje SqrRoot::Invoke. Oto prosta klasa, która przechowuje informacje:

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

Ta klasa implementuje IUnknown, dzięki czemu może być przechowywana w obiekcie wynikowym.

Poniższy kod implementuje metodę 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;
}

Ten kod wykonuje następujące czynności:

  1. Tworzy nowe wystąpienie klasy AsyncOp do przechowywania wartości x.
  2. Wywołuje MFCreateAsyncResult, aby utworzyć obiekt wynikowy. Ten obiekt zawiera kilka wskaźników:
    • Wskaźnik do interfejsu IMFAsyncCallback wywołującego.
    • Wskaźnik do obiektu stanu wywołującego (pState).
    • Wskaźnik do obiektu AsyncOp.
  3. Wywołuje MFPutWorkItem w celu kolejkowania nowego elementu roboczego. To wywołanie niejawnie tworzy obiekt wynikowy zewnętrzny, który zawiera następujące wskaźniki:
    • Wskaźnik do interfejsu IMFAsyncCallback obiektu SqrRoot.
    • Wskaźnik na wewnętrzny obiekt wyniku z kroku 2.

Poniższy kod implementuje metodę 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;
}

Ta metoda pobiera wewnętrzny obiekt wyniku i obiekt AsyncOp. Następnie przekazuje obiekt AsyncOp do DoCalculateSquareRoot. Na koniec wywołuje IMFAsyncResult::SetStatus, aby ustawić kod stanu, a następnie MFInvokeCallback, aby wywołać metodę zwrotną wywołującego.

Metoda DoCalculateSquareRoot dokładnie robi to, czego można się spodziewać.

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

    return S_OK;
}

Gdy wywoływana jest metoda wywołania zwrotnego, obowiązkiem wywołującego jest wywołanie metody end... ... w tym przypadku EndSquareRoot. EndSquareRoot to sposób, w jaki wywołujący pobiera wynik operacji asynchronicznej, która w tym przykładzie jest obliczonym pierwiastkiem kwadratowym. Te informacje są przechowywane w obiekcie wynikowym:

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

Kolejki operacji

Do tej pory przyjęto, że operacja asynchroniczna może być wykonywana w dowolnym momencie, niezależnie od bieżącego stanu obiektu. Rozważmy na przykład, co się stanie, jeśli aplikacja wywołuje BeginSquareRoot, podczas gdy wcześniejsze wywołanie tej samej metody nadal oczekuje. Klasa SqrRoot może umieścić nowy element roboczy w kolejce przed wykonaniem poprzedniego elementu roboczego. Jednak kolejki robocze nie mają gwarancji uporządkowania elementów roboczych w określonej kolejności. Pamiętaj, że kolejka robocza może używać więcej niż jednego wątku do wysyłania elementów roboczych. W środowisku wielowątkowym element roboczy może być wywoływany przed ukończeniem poprzedniego. Elementy robocze mogą być wywoływane niezgodnie z kolejnością, jeśli zmiana kontekstu nastąpi tuż przed uruchomieniem wywołania zwrotnego.

Z tego powodu jest to odpowiedzialność obiektu za serializację operacji na siebie, jeśli jest to wymagane. Innymi słowy, jeśli obiekt wymaga wykonania operacji A przed rozpoczęciem operacji B, obiekt nie może kolejkować elementu roboczego dla B, dopóki operacja A nie zostanie ukończona. Obiekt może spełnić to wymaganie, mając własną kolejkę oczekujących operacji. Gdy metoda asynchroniczna jest wywoływana w obiekcie, obiekt umieszcza żądanie we własnej kolejce. Po zakończeniu każdej operacji asynchronicznej obiekt pobiera kolejne żądanie z kolejki. Przykładowy MPEG1Source pokazuje, jak zaimplementować taką kolejkę.

Pojedyncza metoda może obejmować kilka operacji asynchronicznych, szczególnie w przypadku użycia wywołań wejścia/wyjścia. Podczas implementowania metod asynchronicznych należy dokładnie zastanowić się nad wymaganiami serializacji. Czy na przykład dozwolone jest, aby obiekt rozpoczął nową operację, podczas gdy poprzednie żądanie we/wy jest nadal oczekujące? Jeśli nowa operacja zmieni stan wewnętrzny obiektu, co się stanie, gdy zakończy się poprzednie żądanie we/wy i zwróci dane, które mogą być teraz nieaktualne? Dobry diagram stanu może pomóc w zidentyfikowaniu prawidłowych przejść stanu.

Zagadnienia dotyczące współdziałania wątków i procesów

Kolejki robocze nie używają serializacji COM do przekazywania wskaźników interfejsu pomiędzy wątkami. W związku z tym, nawet jeśli obiekt jest zarejestrowany jako mieszkanie wątkowe lub wątek aplikacji wszedł do mieszkania jednowątkowego (STA), IMFAsyncCallback wywołania zwrotne będą wywoływane z innego wątku. W każdym razie wszystkie składniki potoku programu Media Foundation powinny używać modelu wątkowego "Oba".

Niektóre interfejsy w programie Media Foundation definiują zdalne wersje niektórych metod asynchronicznych. Gdy jedna z tych metod jest wywoływana przez granice procesów, serwer proxy/biblioteka DLL klasy stub programu Media Foundation wywołuje zdalną wersję metody, która wykonuje niestandardowe przeprowadzanie marshalingu parametrów metody. W procesie zdalnym szkielet przekształca wywołanie z powrotem na lokalną metodę obiektu. Ten proces jest przezroczysty zarówno dla aplikacji, jak i obiektu zdalnego. Te niestandardowe metody marshalingu są udostępniane głównie dla obiektów ładowanych w ścieżce chronionego nośnika (PMP). Aby uzyskać więcej informacji na temat protokołu PMP, zobacz Ścieżka chronionego nośnika.

asynchroniczne metody wywołania zwrotnego

Kolejek roboczych