Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Este tópico descreve como implementar um método assíncrono no Microsoft Media Foundation.
Os métodos assíncronos são omnipresentes no fluxo de trabalho do Media Foundation. Os métodos assíncronos facilitam a distribuição do trabalho entre vários threads. É particularmente importante executar E/S de forma assíncrona, para que a leitura de um arquivo ou rede não bloqueie o restante do pipeline.
Se você estiver escrevendo uma fonte de mídia ou um coletor de mídia, é crucial lidar com operações assíncronas corretamente, porque o desempenho do componente tem um impacto em todo o pipeline.
Observação
As transformações do Media Foundation (MFTs) usam métodos síncronos por padrão.
Filas de trabalho para operações assíncronas
No Media Foundation, há uma relação estreita entre métodos de retorno de chamada assíncronos e filas de trabalho . Uma fila de trabalho é uma abstração para mover o trabalho do thread do chamador para um thread de trabalho. Para realizar trabalho numa fila de trabalho, faça o seguinte:
Implemente a interface IMFAsyncCallback.
Chame MFCreateAsyncResult para criar um objeto de resultado . O objeto result expõe o IMFAsyncResult. O objeto de resultado contém três ponteiros:
- Um ponteiro para a interface IMFAsyncCallback do chamador.
- Um ponteiro opcional para um objeto de estado. Se especificado, o objeto de estado deve implementar IUnknown.
- Um ponteiro opcional para um objeto particular. Se especificado, este objeto também deve implementar IUnknown.
Os dois últimos ponteiros podem ser NULL. Caso contrário, use-os para armazenar informações sobre a operação assíncrona.
Ligue MFPutWorkItemEx para adicionar à fila do item de trabalho.
A thread da fila de tarefas chama o seu método IMFAsyncCallback::Invoke.
Faça o trabalho dentro do seu Invoke método. O parâmetro pAsyncResult desse método é o ponteiro IMFAsyncResult da etapa 2. Use este ponteiro para obter o objeto de estado e o objeto privado:
- Para obter o objeto state, chame IMFAsyncResult::GetState.
- Para obter o objeto privado, chame IMFAsyncResult::GetObject.
Como alternativa, pode combinar as etapas 2 e 3 chamando a função MFPutWorkItem. Internamente, essa função chama MFCreateAsyncResult para criar o objeto result.
O diagrama a seguir mostra as relações entre o chamador, o objeto result, o objeto state e o objeto private.
O diagrama de sequência a seguir mostra como um objeto enfileira um item de trabalho. Quando a fila de trabalho chama Invoke, o objeto executa a operação assíncrona nessa fila.
É importante lembrar que Invoke é chamado a partir de uma thread que pertence à fila de trabalho. Sua implementação do Invoke deve ser thread safe. Além disso, se utilizares a fila de trabalho da plataforma (MFASYNC_CALLBACK_QUEUE_STANDARD), é fundamental que nunca bloqueies a thread, porque isso pode bloquear todo o pipeline do Media Foundation de processar dados. Se precisar executar uma operação que bloqueie ou demore muito tempo a concluir, utilize uma fila de trabalho privada. Para criar uma fila de trabalho privada, chame MFAllocateWorkQueue. Qualquer componente de pipeline que execute operações de E/S deve evitar bloquear chamadas de E/S pelo mesmo motivo. A interface IMFByteStream fornece uma abstração útil para E/S de arquivo assíncrono.
Implementando o Begin.../End... Padrão
Conforme descrito em Calling Asynchronous Methods, os métodos assíncronos no Media Foundation geralmente usam o padrão Begin.../End..... Nesse padrão, uma operação assíncrona usa dois métodos com assinaturas semelhantes às seguintes:
// 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);
Para tornar o método verdadeiramente assíncrono, a implementação do BeginX deve executar o trabalho real em outro thread. É aqui que entram em cena as filas de trabalho. Nas etapas a seguir, o chamador é o código que chama BeginX e EndX. Isso pode ser um aplicativo ou o pipeline do Media Foundation. O componente é o código que implementa BeginX e EndX.
- O chamador chama Begin..., passando um ponteiro para a interface deIMFAsyncCallbackdo chamador.
- O componente cria um novo objeto de resultado assíncrono. Este objeto armazena a interface de retorno de chamada e o objeto de estado do chamador. Normalmente, ele também armazena todas as informações de estado privado que o componente precisa para concluir a operação. O objeto de resultado desta etapa é rotulado como "Resultado 1" no diagrama seguinte.
- O componente cria um segundo objeto de resultado. Esse objeto de resultado armazena dois ponteiros: o primeiro objeto de resultado e a interface de retorno de chamada do destinatário. Este objeto de resultado é rotulado como "Resultado 2" no diagrama seguinte.
- O componente chama MFPutWorkItemEx para enfileirar um novo item de trabalho.
- No método Invoke, o componente faz o trabalho assíncrono.
- O componente chama MFInvokeCallback para invocar o método de retorno de chamada do chamador.
- O chamador invoca o método EndX.
Exemplo de método assíncrono
Para ilustrar esta discussão, usaremos um exemplo inventado. Considere um método assíncrono para calcular uma raiz quadrada:
HRESULT BeginSquareRoot(double x, IMFAsyncCallback *pCB, IUnknown *pState);
HRESULT EndSquareRoot(IMFAsyncResult *pResult, double *pVal);
O parâmetro x de BeginSquareRoot é o valor cuja raiz quadrada será calculada. A raiz quadrada é retornada como o parâmetro pVal de EndSquareRoot.
Aqui está a declaração de uma classe que implementa esses dois métodos:
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);
};
A classe SqrRoot implementa IMFAsyncCallback para que ela possa colocar a operação de raiz quadrada numa lista de trabalhos. O método DoCalculateSquareRoot é o método de classe privada que calcula a raiz quadrada. Esse método será chamado a partir do thread da fila de trabalho.
Primeiro, precisamos de uma maneira de armazenar o valor de x, para que ele possa ser recuperado quando o thread da fila de trabalho chamar SqrRoot::Invoke. Aqui está uma classe simples que armazena as informações:
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;
}
};
Essa classe implementa IUnknown para que ele possa ser armazenado em um objeto de resultado.
O código a seguir implementa o método 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;
}
Este código faz o seguinte:
- Cria uma nova instância da classe
AsyncOppara manter o valor de x. - Chama MFCreateAsyncResult para criar um objeto de resultado. Este objeto contém vários ponteiros:
- Um ponteiro para a interface deIMFAsyncCallbackdo chamador.
- Um ponteiro para o objeto de estado do chamador (pState).
- Um ponteiro para o objeto
AsyncOp.
- Chama MFPutWorkItem para enfileirar um novo item de trabalho. Essa chamada cria implicitamente um objeto de resultado externo, que contém os seguintes ponteiros:
- Um ponteiro para a interfaceIMFAsyncCallbackdo objeto
SqrRoot. - Um ponteiro para o objeto de resultado interno proveniente da etapa 2.
- Um ponteiro para a interfaceIMFAsyncCallbackdo objeto
O código a seguir implementa o método 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;
}
Este método obtém o objeto de resultado interno e o objeto AsyncOp. Em seguida, ele passa o objeto AsyncOp para DoCalculateSquareRoot. Finalmente, ele chama IMFAsyncResult::SetStatus para definir o código de status e MFInvokeCallback para invocar o método de retorno de chamada do chamador.
O método DoCalculateSquareRoot faz exatamente o que você esperaria:
HRESULT SqrRoot::DoCalculateSquareRoot(AsyncOp *pOp)
{
pOp->m_value = sqrt(pOp->m_value);
return S_OK;
}
Quando o método de retorno de chamada do chamador é invocado, é responsabilidade dele executar o método End... — neste caso, EndSquareRoot. O EndSquareRoot é como o chamador recupera o resultado da operação assíncrona, que neste exemplo é a raiz quadrada computada. Essas informações são armazenadas no objeto de resultado:
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;
}
Filas de Operação
Até agora, foi tacitamente assumido que uma operação assíncrona poderia ser feita a qualquer momento, independentemente do estado atual do objeto. Por exemplo, considere o que acontece se um aplicativo chamar BeginSquareRoot enquanto uma chamada anterior para o mesmo método ainda estiver pendente. A classe SqrRoot pode enfileirar o novo item de trabalho antes que o item de trabalho anterior seja concluído. No entanto, não se garante que as filas de trabalho façam a serialização dos itens de trabalho. Lembre-se de que uma fila de trabalho pode usar mais de um thread para despachar itens de trabalho. Em um ambiente multithreaded, um item de trabalho pode ser invocado antes que o anterior tenha sido concluído. Os itens de trabalho podem até ser invocados fora de ordem, se ocorrer uma mudança de contexto pouco antes de o callback ser acionado.
Por esse motivo, é responsabilidade do objeto serializar operações em si mesmo, se necessário. Em outras palavras, se o objeto exigir que a operação A seja concluída antes que a operação B possa ser iniciada, o objeto não deverá enfileirar um item de trabalho para B até que a operação A seja concluída. Um objeto pode atender a esse requisito tendo sua própria fila de operações pendentes. Quando um método assíncrono é chamado no objeto, o objeto coloca a solicitação em sua própria fila. À medida que cada operação assíncrona é concluída, o objeto extrai a próxima solicitação da fila. O MPEG1Source Sample mostra um exemplo de como implementar essa fila.
Um único método pode envolver várias operações assíncronas, particularmente quando chamadas de E/S são usadas. Ao implementar métodos assíncronos, pense cuidadosamente nos requisitos de serialização. Por exemplo, é válido para o objeto iniciar uma nova operação enquanto uma solicitação de E/S anterior ainda está pendente? Se a nova operação alterar o estado interno do objeto, o que acontece quando uma solicitação de E/S anterior é concluída e retorna dados que agora podem estar obsoletos? Um bom diagrama de estado pode ajudar a identificar as transições de estado válidas.
Considerações sobre threads e processos cruzados
As filas de trabalho não usam marshaling COM para organizar ponteiros de interface através dos limites de thread. Portanto, mesmo que um objeto seja registado como apartment-threaded ou o thread da aplicação tenha entrado num apartamento single-threaded (STA), callbacks IMFAsyncCallback serão invocados a partir de um thread diferente. Em qualquer caso, todos os componentes do fluxo de processamento da Media Foundation devem usar o modelo de "Both" threading.
Algumas interfaces no Media Foundation definem versões remotas de alguns métodos assíncronos. Quando um destes métodos é chamado através dos limites do processo, a DLL proxy/stub do Media Foundation chama a versão remota do método, que executa a marshalização personalizada dos parâmetros do método. No processo remoto, o stub traduz a chamada para o método local no objeto. Esse processo é transparente para o aplicativo e para o objeto remoto. Esses métodos de empacotamento personalizados são fornecidos principalmente para objetos que são carregados no caminho de mídia protegido (PMP). Para obter mais informações sobre o PMP, consulte Protected Media Path.
Tópicos relacionados