다음을 통해 공유


비동기 메서드 작성

이 항목에서는 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 호출하여 결과 개체를 만듭니다.

다음 다이어그램은 호출자, 결과 개체, 상태 개체 및 개인 개체 간의 관계를 보여 있습니다.

비동기 결과 개체다이어그램

다음 시퀀스 다이어그램은 개체가 작업 항목을 큐에 대기하는 방법을 보여 줍니다. 작업 큐 스레드가 을 호출할 때에 대해 개체는 해당 스레드에서 비동기 작업을 수행합니다.

개체가 작업 항목을 큐에 대기하는 방법을 보여 주는 다이어그램

작업 큐가 소유한 스레드에서 호출을 기억하고가 호출됨을 인지해야 합니다. 호출 구현은 스레드 안전성을 유지해야 합니다. 또한 플랫폼 작업 큐(MFASYNC_CALLBACK_QUEUE_STANDARD)를 사용하는 경우 전체 Media Foundation 파이프라인이 데이터를 처리하지 못하도록 차단할 수 있으므로 스레드를 차단하지 않는 것이 중요합니다. 작업을 차단하거나 완료하는 데 시간이 오래 걸리는 작업을 수행해야 하는 경우 프라이빗 작업 큐를 사용합니다. 프라이빗 작업 큐를 만들려면 MFAllocateWorkQueue호출합니다. I/O 작업을 수행하는 파이프라인 구성 요소는 동일한 이유로 I/O 호출을 차단하지 않아야 합니다. IMFByteStream 인터페이스는 비동기 파일 I/O에 대한 유용한 추상화 기능을 제공합니다.

시작/종료 패턴 구현하기

호출 비동기 메서드설명한 대로 Media Foundation의 비동기 메서드는 종종 Begin.../End.... 패턴을 사용합니다. 이 패턴에서 비동기 작업은 다음과 유사한 서명이 있는 두 가지 메서드를 사용합니다.

// 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. 구성 요소는 두 번째 결과 개체를 만듭니다. 이 결과 개체는 첫 번째 결과 개체와 호출 수신자의 콜백 인터페이스라는 두 개의 포인터를 저장합니다. 이 결과 개체는 다음 다이어그램에서 "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 매개변수는 제곱근이 계산될 값입니다. 제곱근은 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 클래스의 새 인스턴스를 만듭니다.
  2. MFCreateAsyncResult호출하여 결과 개체를 만듭니다. 이 개체에는 다음과 같은 여러 포인터가 있습니다.
    • 호출자의 IMFAsyncCallback 인터페이스에 대한 포인터입니다.
    • 호출자의 상태 개체(pState)에 대한 포인터입니다.
    • AsyncOp 개체에 대한 포인터입니다.
  3. MFPutWorkItem 호출하여 새 작업 항목을 큐에 추가합니다. 이 호출은 다음 포인터를 포함하는 외부 결과 개체를 암시적으로 만듭니다.
    • SqrRoot 개체의 IMFAsyncCallback 인터페이스에 대한 포인터입니다.
    • 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 개체를 가져옵니다. 그런 다음 AsyncOp 개체를 DoCalculateSquareRoot에 전달합니다. 마지막으로 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에 대한 자세한 내용은 보호된 미디어 경로참조하세요.

비동기 콜백 메서드

작업 큐