비동기 메서드 작성

이 항목에서는 Microsoft Media Foundation에서 비동기 메서드를 구현하는 방법을 설명합니다.

비동기 메서드는 Media Foundation 파이프라인에서 유비쿼터스입니다. 비동기 메서드를 사용하면 여러 스레드 간에 작업을 보다 쉽게 배포할 수 있습니다. 파일 또는 네트워크에서 읽는 것이 파이프라인의 나머지 부분을 차단하지 않도록 I/O를 비동기적으로 수행하는 것이 특히 중요합니다.

미디어 원본 또는 미디어 싱크를 작성하는 경우 구성 요소의 성능이 전체 파이프라인에 영향을 주므로 비동기 작업을 올바르게 처리하는 것이 중요합니다.

참고

MFT(Media Foundation 변환)는 기본적으로 동기 메서드를 사용합니다.

 

비동기 작업에 대한 작업 큐

Media Foundation에는 비동기 콜백 메서드작업 큐 간에 긴밀한 관계가 있습니다. 작업 큐는 호출자의 스레드에서 작업자 스레드로 작업을 이동하기 위한 추상화입니다. 작업 큐에서 작업을 수행하려면 다음을 수행합니다.

  1. IMFAsyncCallback 인터페이스를 구현합니다.

  2. MFCreateAsyncResult를 호출하여 결과 개체를 만듭니다. 결과 개체는 IMFAsyncResult를 노출합니다. 결과 개체에는 세 개의 포인터가 포함됩니다.

    • 호출자의 IMFAsyncCallback 인터페이스에 대한 포인터입니다.
    • 상태 개체에 대한 선택적 포인터입니다. 지정된 경우 상태 개체는 IUnknown을 구현해야 합니다.
    • 개인 개체에 대한 선택적 포인터입니다. 지정한 경우 이 개체도 IUnknown을 구현해야 합니다.

    마지막 두 포인터는 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.../을 사용합니다. 끝.... 패턴. 이 패턴에서 비동기 작업은 다음과 유사한 서명이 있는 두 가지 메서드를 사용합니다.

// 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 파이프라인일 수 있습니다. 구성 요소는BeginXEndX를 구현하는 코드입니다.

  1. 호출자는 Begin...을 호출하여 호출자의 IMFAsyncCallback 인터페이스에 대한 포인터를 전달합니다.
  2. 구성 요소는 새 비동기 결과 개체를 만듭니다. 이 개체는 호출자의 콜백 인터페이스 및 상태 개체를 저장합니다. 일반적으로 구성 요소가 작업을 완료하는 데 필요한 모든 개인 상태 정보도 저장합니다. 이 단계의 결과 개체는 다음 다이어그램에서 "결과 1"이라는 레이블이 지정됩니다.
  3. 구성 요소는 두 번째 결과 개체를 만듭니다. 이 결과 개체는 첫 번째 결과 개체와 호출 수신자의 콜백 인터페이스라는 두 개의 포인터를 저장합니다. 이 결과 개체는 다음 다이어그램에서 "결과 2"라는 레이블이 지정됩니다.
  4. 구성 요소는 MFPutWorkItemEx 를 호출하여 새 작업 항목을 큐에 추가합니다.
  5. Invoke 메서드에서 구성 요소는 비동기 작업을 수행합니다.
  6. 구성 요소는 MFInvokeCallback 을 호출하여 호출자의 콜백 메서드를 호출합니다.
  7. 호출자는 EndX 메서드를 호출합니다.

개체가 시작/끝 패턴을 구현하는 방법을 보여 주는 다이어그램

비동기 메서드 예제

이 토론을 설명하기 위해 고안된 예제를 사용합니다. 제곱근을 계산하는 비동기 메서드를 고려합니다.

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

BeginSquareRoot x 매개 변수는 제곱근이 계산되는 값입니다. 제곱근은 의 EndSquareRootpVal 매개 변수에 반환됩니다.

다음은 이러한 두 메서드를 구현하는 클래스의 선언입니다.

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 제곱근을 계산하는 private 클래스 메서드입니다. 이 메서드는 작업 큐 스레드에서 호출됩니다.

먼저 작업 큐 스레드가 를 호출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 새 instance 만듭니다.
  2. MFCreateAsyncResult를 호출하여 결과 개체를 만듭니다. 이 개체에는 다음과 같은 여러 포인터가 있습니다.
    • 호출자의 IMFAsyncCallback 인터페이스에 대한 포인터입니다.
    • 호출자의 상태 개체(pState)에 대한 포인터입니다.
    • AsyncOp 개체에 대한 포인터입니다.
  3. MFPutWorkItem을 호출하여 새 작업 항목을 큐에 추가합니다. 이 호출은 다음 포인터를 포함하는 외부 결과 개체를 암시적으로 만듭니다.
    • 개체의 IMFAsyncCallback 인터페이스에 대한 포인터 SqrRoot 입니다.
    • 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 가져옵니다. 그런 다음 개체DoCalculateSquareRootAsyncOp 에 전달합니다. 마지막으로 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 샘플은 이러한 큐를 구현하는 방법의 예를 보여줍니다.

단일 메서드에는 특히 I/O 호출이 사용되는 경우 여러 비동기 작업이 포함될 수 있습니다. 비동기 메서드를 구현할 때는 serialization 요구 사항을 신중하게 고려해야 합니다. 예를 들어 이전 I/O 요청이 아직 보류 중인 동안 개체가 새 작업을 시작하는 것이 유효한가요? 새 작업이 개체의 내부 상태를 변경하는 경우 이전 I/O 요청이 완료되고 부실할 수 있는 데이터를 반환하면 어떻게 되나요? 좋은 상태 다이어그램은 유효한 상태 전환을 식별하는 데 도움이 될 수 있습니다.

교차 스레드 및 프로세스 간 고려 사항

작업 큐는 COM 마샬링을 사용하여 스레드 경계를 넘어 인터페이스 포인터를 마샬링하지 않습니다. 따라서 개체가 아파트 스레드로 등록되거나 애플리케이션 스레드가 STA(단일 스레드 아파트)에 진입한 경우에도 IMFAsyncCallback 콜백 은 다른 스레드에서 호출됩니다. 어떤 경우든 모든 Media Foundation 파이프라인 구성 요소는 "둘 다" 스레딩 모델을 사용해야 합니다.

Media Foundation의 일부 인터페이스는 일부 비동기 메서드의 원격 버전을 정의합니다. 이러한 메서드 중 하나가 프로세스 경계를 넘어 호출되면 Media Foundation 프록시/스텁 DLL은 메서드 매개 변수의 사용자 지정 마샬링을 수행하는 메서드의 원격 버전을 호출합니다. 원격 프로세스에서 스텁은 호출을 개체의 로컬 메서드로 다시 변환합니다. 이 프로세스는 애플리케이션과 원격 개체 모두에 투명합니다. 이러한 사용자 지정 마샬링 메서드는 주로 PMP(보호된 미디어 경로)에 로드된 개체에 대해 제공됩니다. PMP에 대한 자세한 내용은 보호된 미디어 경로를 참조하세요.

비동기 콜백 메서드

작업 큐