Поделиться через


Написание асинхронного метода

В этом разделе описывается реализация асинхронного метода в Microsoft Media Foundation.

Асинхронные методы широко распространены в конвейере Media Foundation. Асинхронные методы упрощают распределение работы между несколькими потоками. Особенно важно асинхронно выполнять операции ввода-вывода, чтобы чтение из файла или сети не блокирует остальную часть конвейера.

Если вы пишете источник мультимедиа или приемник мультимедиа, важно правильно обрабатывать асинхронные операции, так как производительность компонента влияет на весь конвейер.

Заметка

Преобразования Media Foundation (MFTs) используют синхронные методы по умолчанию.

 

Рабочие очереди для асинхронных операций

В Media Foundation существует тесная связь между асинхронными методами обратного вызова и рабочими очередями. Рабочая очередь — это абстракция для переноса задач из потока вызывающего объекта в рабочий поток. Чтобы выполнить работу в рабочей очереди, сделайте следующее:

  1. Реализуйте интерфейс IMFAsyncCallback.

  2. Вызовите MFCreateAsyncResult для создания объекта результата . Объект результата предоставляет интерфейс IMFAsyncResult. Результирующий объект содержит три указателя:

    • Указатель на интерфейс IMFAsyncCallback вызывающего абонента.
    • Необязательный указатель на объект состояния. Если задано, объект состояния должен реализовать IUnknown.
    • Необязательный указатель на частный объект. Если указано, этот объект также должен реализовать IUnknown.

    Последние два указателя могут быть NULL. В противном случае используйте их для хранения сведений об асинхронной операции.

  3. Вызовите MFPutWorkItemEx в очередь к рабочему элементу.

  4. Поток в очереди задач вызывает ваш метод IMFAsyncCallback::Invoke.

  5. Выполните работу внутри метода Invoke. Параметр pAsyncResult этого метода является указателем IMFAsyncResult на шаге 2. Используйте этот указатель, чтобы получить объект состояния и частный объект:

В качестве альтернативы можно объединить шаги 2 и 3, вызвав функцию MFPutWorkItem. Во внутреннем окне эта функция вызывает MFCreateAsyncResult для создания объекта результата.

На следующей схеме показаны связи между вызывающим объектом, объектом результата, объектом состояния и частным объектом.

схема с асинхронным объектом результатов

На следующей схеме последовательности показано, как объект ставит в очередь рабочий элемент. Когда поток рабочей очереди вызывает Invoke, объект выполняет асинхронную операцию в этом потоке.

схема, показывающая, как объект помещает в очередь рабочий элемент

Важно помнить, Вызов вызывается из потока, принадлежащий рабочей очереди. Реализация Invoke должна быть потокобезопасной. Кроме того, если вы используете рабочую очередь платформы (MFASYNC_CALLBACK_QUEUE_STANDARD), необходимо, чтобы поток никогда не блокировался, поскольку это может блокировать весь конвейер Media Foundation от обработки данных. Если вам нужно выполнить операцию, которая будет блокировать выполнение или займет много времени, используйте частную рабочую очередь. Чтобы создать частную рабочую очередь, вызовите MFAllocateWorkQueue. Любой компонент конвейера, выполняющий операции ввода-вывода, должен избежать блокировки вызовов ввода-вывода по той же причине. Интерфейс IMFByteStream предоставляет полезную абстракцию для асинхронного ввода-вывода файлов.

Реализация шаблона Begin.../End...

Как описано в описании вызова асинхронных методов, асинхронные методы в 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 должна выполнять фактическую работу с другим потоком. Вот где рабочие очереди вступают в дело. В следующих шагах код вызывающий — это тот, который вызывает BeginX и EndX. Это может быть приложение или система Media Foundation. Компонент — это код, который реализует BeginX и EndX.

  1. Вызывающий вызывает Begin..., передавая указатель на интерфейс IMFAsyncCallback вызывающего.
  2. Компонент создает новый асинхронный объект результата. Этот объект сохраняет интерфейс обратного вызова вызывающего и объект состояния. Как правило, он также сохраняет любые частные сведения о состоянии, необходимые компоненту для выполнения операции. Результирующий объект на этом шаге помечен как "Result 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);

Параметр xBeginSquareRoot — это значение, квадратный корень которого будет вычисляться. Квадратный корень возвращается в параметре pVal из EndSquareRoot.

Ниже приведено объявление класса, реализующего эти два метода:

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 — это метод частного класса, который вычисляет квадратный корень. Этот метод будет вызван из потока рабочей очереди.

Во-первых, необходимо сохранить значение x, чтобы его можно было извлечь, когда поток рабочей очереди вызывает SqrRoot::Invoke. Ниже приведен простой класс, в который хранятся сведения:

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. Создает новый экземпляр класса AsyncOp для хранения значения x.
  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. Затем он передает объект 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 может поставить в очередь новый рабочий элемент до завершения предыдущего. Однако рабочие очереди не гарантируют сериализацию рабочих элементов. Помните, что рабочая очередь может использовать несколько потоков для передачи рабочих элементов. В многопоточной среде рабочий элемент может вызываться до завершения предыдущей. Рабочие элементы могут даже вызываться в произвольном порядке, если переключение контекста происходит непосредственно перед срабатыванием обратного вызова.

По этой причине, в случае необходимости, объект несет ответственность за сериализацию операций, выполняемых им. Другими словами, если объекту необходимо, чтобы операция A завершилась до начала операции B, объект не должен ставить рабочий элемент для B до завершения операции A. Объект может соответствовать этому требованию, имея собственную очередь ожидающих операций. При вызове асинхронного метода объекта объект помещает запрос в собственную очередь. По мере завершения каждой асинхронной операции объект извлекает следующий запрос из очереди. Пример MPEG1Source показывает, как реализовать такую очередь.

Один метод может включать несколько асинхронных операций, особенно при использовании вызовов ввода-вывода. При реализации асинхронных методов тщательно подумайте о требованиях сериализации. Например, допустимо ли начать новую операцию, если предыдущий запрос ввода-вывода остается в ожидании? Если новая операция изменяет внутреннее состояние объекта, что происходит, когда предыдущий запрос ввода-вывода завершается и возвращает данные, которые теперь могут быть устаревшими? Хорошая схема состояния может помочь определить допустимые переходы состояния.

Особенности межпоточного и межпроцессного взаимодействия

Рабочие очереди не используют маршалинг COM для маршалирования указателей интерфейса через границы потока. Таким образом, даже если объект зарегистрирован как apartment-threaded или поток приложения вошел в single-threaded apartment (STA), IMFAsyncCallback обратные вызовы будут вызываться из другого потока. В любом случае все компоненты конвейера Media Foundation должны использовать модель потоков "Оба".

Некоторые интерфейсы в Media Foundation определяют удаленные версии некоторых асинхронных методов. При вызове одного из этих методов между процессами библиотека-посредник/заглушка DLL Media Foundation вызывает удаленную версию метода, которая выполняет настраиваемую передачу параметров метода. В удаленном процессе заглушка преобразует вызов обратно в локальный метод объекта. Этот процесс является прозрачным как для приложения, так и для удаленного объекта. Эти пользовательские методы маршалинга предоставляются в основном для объектов, загруженных в защищенный путь мультимедиа (PMP). Дополнительные сведения о PMP см. в разделе Путь к защищенному носителю.

Методы асинхронного обратного вызова

очереди задач