非同期メソッドの記述

このトピックでは、Microsoft Media Foundation で非同期メソッドを実装する方法について説明します。

非同期メソッドは、Media Foundation パイプラインでユビキタスです。 非同期メソッドを使用すると、複数のスレッド間で作業を簡単に分散できます。 ファイルまたはネットワークからの読み取りによってパイプラインの残りの部分がブロックされないように、I/O を非同期的に実行することが特に重要です。

メディア ソースまたはメディア シンクを作成する場合は、コンポーネントのパフォーマンスがパイプライン全体に影響を与えるので、非同期操作を正しく処理することが重要です。

注意

Media Foundation 変換 (MFT) では、既定で同期メソッドが使用されます。

 

非同期操作の作業キュー

Media Foundation では、 非同期コールバック メソッド作業キューの間に密接な関係があります。 作業キューは、呼び出し元のスレッドからワーカー スレッドに作業を移動するための抽象化です。 作業キューで作業を実行するには、次の操作を行います。

  1. IMFAsyncCallback インターフェイスを実装します。

  2. MFCreateAsyncResult を呼び出して、結果オブジェクトを作成します。 結果オブジェクトは、 IMFAsyncResult を公開します。 結果オブジェクトには、次の 3 つのポインターが含まれています。

    • 呼び出し元の IMFAsyncCallback インターフェイスへのポインター。
    • 状態オブジェクトへの省略可能なポインター。 指定した場合、状態オブジェクトは IUnknown を実装する必要があります。
    • プライベート オブジェクトへの省略可能なポインター。 指定した場合、このオブジェクトは IUnknown も実装する必要があります。

    最後の 2 つのポインターは NULL にすることができます。 それ以外の場合は、非同期操作に関する情報を保持するために使用します。

  3. MFPutWorkItemEx を呼び出して、作業項目にキューを作成します。

  4. ワーク キュー スレッドは、 IMFAsyncCallback::Invoke メソッドを呼び出します。

  5. Invoke メソッド内で作業を行います。 このメソッドの pAsyncResult パラメーターは、手順 2 の IMFAsyncResult ポインターです。 状態オブジェクトとプライベート オブジェクトを取得するには、次のポインターを使用します。

別の方法として、 MFPutWorkItem 関数を呼び出して手順 2 と 3 を組み合わせることができます。 内部的には、この関数は MFCreateAsyncResult を呼び出して結果オブジェクトを作成します。

次の図は、呼び出し元、結果オブジェクト、状態オブジェクト、プライベート オブジェクトの関係を示しています。

非同期の結果オブジェクトを示す図

次のシーケンス図は、オブジェクトが作業項目をキューに入れる方法を示しています。 作業キュー スレッドが Invoke を呼び出すと、オブジェクトはそのスレッドに対して非同期操作を実行します。

オブジェクトが作業項目をキューに入れる方法を示す図

Invoke は、作業キューによって所有されているスレッドから呼び出されることを覚えておくことを重要にします。 Invoke の実装はスレッド セーフである必要があります。 さらに、プラットフォーム作業キュー (MFASYNC_CALLBACK_QUEUE_STANDARD) を使用する場合は、Media Foundation パイプライン全体でデータの処理がブロックされる可能性があるため、スレッドをブロックしないようにすることが重要です。 ブロックまたは完了に長い時間がかかる操作を実行する必要がある場合は、プライベート作業キューを使用します。 プライベート作業キューを作成するには、 MFAllocateWorkQueue を呼び出します。 I/O 操作を実行するパイプライン コンポーネントは、同じ理由で I/O 呼び出しをブロックしないようにする必要があります。 IMFByteStream インターフェイスは、非同期ファイル I/O に役立つ抽象化を提供します。

Begin.../End... の実装パターン

「非同期メソッドの呼び出し」で説明されているように、Media Foundation の非同期メソッドでは、多くの場合、Begin... を使用します。/終わり。。。。パターン。 このパターンでは、非同期操作では、次のようなシグネチャを持つ 2 つのメソッドが使用されます。

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

メソッドを真に非同期にするには、 BeginX の実装で別のスレッドで実際の作業を実行する必要があります。 ここで作業キューが画像に表示されます。 次の手順では、 呼び出し元BeginXEndX を呼び出すコードです。 これは、アプリケーションまたは Media Foundation パイプラインである可能性があります。 コンポーネントは、BeginX と EndX を実装するコードです

  1. 呼び出し元は Begin...を呼び出し、呼び出し元の IMFAsyncCallback インターフェイスへのポインターを渡します。
  2. コンポーネントは、新しい非同期結果オブジェクトを作成します。 このオブジェクトは、呼び出し元のコールバック インターフェイスと状態オブジェクトを格納します。 通常は、コンポーネントが操作を完了するために必要なプライベート状態情報も格納されます。 この手順の結果オブジェクトには、次の図の "Result 1" というラベルが付けられます。
  3. コンポーネントは、2 つ目の結果オブジェクトを作成します。 この結果オブジェクトには、最初の結果オブジェクトと呼び出し先のコールバック インターフェイスという 2 つのポインターが格納されます。 次の図では、この結果オブジェクトに "Result 2" というラベルが付けられます。
  4. コンポーネントは MFPutWorkItemEx を呼び出して、新しい作業項目をキューに入れます。
  5. Invoke メソッドでは、コンポーネントは非同期処理を行います。
  6. コンポーネントは MFInvokeCallback を 呼び出して、呼び出し元のコールバック メソッドを呼び出します。
  7. 呼び出し元は EndX メソッドを呼び出します。

オブジェクトが開始/終了パターンを実装する方法を示す図

非同期メソッドの例

この議論を説明するために、工夫された例を使用します。 平方根を計算するための非同期メソッドを考えてみましょう。

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

BeginSquareRootx パラメーターは、平方根が計算される値です。 平方根は、 の pVal パラメーター EndSquareRootで返されます。

この 2 つのメソッドを実装するクラスの宣言を次に示します。

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

クラスは SqrRoot 、作業キューに平方根操作を配置できるように 、IMFAsyncCallback を実装します。 メソッドは DoCalculateSquareRoot 、平方根を計算するプライベート クラス メソッドです。 このメソッドは、作業キュー スレッドから呼び出されます。

最初に、作業キュー スレッドが を呼び出SqrRoot::Invokeしたときに取得できるように、x の値を格納する方法が必要です。 情報を格納する単純なクラスを次に示します。

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

このクラスは、結果オブジェクトに格納できるように IUnknown を実装します。

次のコードは、 メソッドを 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;
}

このコードは、次の処理を実行します。

  1. x の値を AsyncOp 保持する クラスの新しいインスタンスを作成 します
  2. MFCreateAsyncResult を呼び出して、結果オブジェクトを作成します。 このオブジェクトは、いくつかのポインターを保持します。
    • 呼び出し元の IMFAsyncCallback インターフェイスへのポインター。
    • 呼び出し元の状態オブジェクト (pState) へのポインター。
    • AsyncOp オブジェクトを指すポインター。
  3. MFPutWorkItem を呼び出して、新しい作業項目をキューに入れます。 この呼び出しは、次のポインターを保持する外部の結果オブジェクトを暗黙的に作成します。
    • オブジェクトの SqrRootIMFAsyncCallback インターフェイスへのポインター。
    • 手順 2 の内部結果オブジェクトへのポインター。

次のコードは、 メソッドを 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;
}

このメソッドは、内部の結果オブジェクトと オブジェクトを AsyncOp 取得します。 次に、 オブジェクトを にAsyncOpDoCalculateSquareRoot渡します。 最後に、 IMFAsyncResult::SetStatus を呼び出して状態コードを設定し、 MFInvokeCallback を呼び出して呼び出し元のコールバック メソッドを呼び出します。

メソッドは DoCalculateSquareRoot 、期待どおりの処理を行います。

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

    return S_OK;
}

呼び出し元のコールバック メソッドが呼び出されると、呼び出し元は End... メソッド (この場合 EndSquareRootは ) を呼び出す必要があります。 は EndSquareRoot 、呼び出し元が非同期操作の結果を取得する方法です。この例では、計算された平方根です。 この情報は、結果オブジェクトに格納されます。

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

操作キュー

ここまでは、オブジェクトの現在の状態に関係なく、非同期操作をいつでも実行できると暗黙に想定してきました。 たとえば、同じメソッドに対する以前の呼び出しがまだ保留中の状態でアプリケーションがを呼び出 BeginSquareRoot した場合はどうなるかを考えてみましょう。 クラスは SqrRoot 、前の作業項目が完了する前に、新しい作業項目をキューに入れます。 ただし、作業キューで作業項目をシリアル化することは保証されません。 作業キューで複数のスレッドを使用して作業項目をディスパッチできることを思い出してください。 マルチスレッド環境では、前の作業項目が完了する前に作業項目が呼び出されることがあります。 コールバックが呼び出される直前にコンテキスト切り替えが発生した場合は、作業項目を順に呼び出すこともできます。

このため、必要に応じて、オブジェクト自体で操作をシリアル化するのはオブジェクトの責任です。 つまり、操作 B を開始する前にオブジェクトが操作 A を終了する必要がある場合、操作 A が完了するまで、オブジェクトは B の作業項目をキューに入れる必要はありません。 オブジェクトは、保留中の操作の独自のキューを持つことで、この要件を満たすことができます。 オブジェクトに対して非同期メソッドが呼び出されると、オブジェクトは要求を独自のキューに配置します。 各非同期操作が完了すると、オブジェクトはキューから次の要求をプルします。 MPEG1Source サンプルは、このようなキューを実装する方法の例を示しています。

1 つのメソッドには、特に I/O 呼び出しが使用される場合に、いくつかの非同期操作が含まれる場合があります。 非同期メソッドを実装する場合は、シリアル化の要件について慎重に検討してください。 たとえば、以前の I/O 要求がまだ保留中の間にオブジェクトが新しい操作を開始することは有効ですか? 新しい操作によってオブジェクトの内部状態が変更された場合、以前の I/O 要求が完了し、古くなった可能性があるデータが返された場合はどうなりますか? 適切な状態図は、有効な状態遷移を識別するのに役立ちます。

スレッド間とプロセス間の考慮事項

作業キューでは、COM マーシャリングを使用して、スレッド境界を越えてインターフェイス ポインターをマーシャリングしません。 したがって、オブジェクトがアパートメント スレッドとして登録されているか、アプリケーション スレッドがシングルスレッド アパートメント (STA) に入った場合でも、 IMFAsyncCallback コールバックは別のスレッドから呼び出されます。 いずれの場合も、すべての Media Foundation パイプライン コンポーネントで "Both" スレッド モデルを使用する必要があります。

Media Foundation の一部のインターフェイスでは、一部の非同期メソッドのリモート バージョンが定義されています。 これらのメソッドのいずれかがプロセス境界を越えて呼び出されると、Media Foundation プロキシ/スタブ DLL はメソッドのリモート バージョンを呼び出し、メソッド パラメーターのカスタム マーシャリングを実行します。 リモート プロセスでは、スタブは呼び出しを オブジェクトのローカル メソッドに変換します。 このプロセスは、アプリケーションとリモート オブジェクトの両方に対して透過的です。 これらのカスタム マーシャリング メソッドは、主に保護されたメディア パス (PMP) に読み込まれるオブジェクトに対して提供されます。 PMP の詳細については、「 保護されたメディア パス」を参照してください。

非同期コールバック メソッド

作業キュー