Demonstra Passo a passo: Usando o Runtime de simultaneidade em um aplicativo habilitado para COM
Este documento demonstra como usar o Runtime de simultaneidade em um aplicativo que usa o modelo de objeto componente (COM).
Pré-requisitos
Antes de começar este passo a passo, leia os seguintes documentos:
Para obter mais informações sobre o COM, consulte Modelo de objeto componente (COM).
Gerenciar a vida útil da biblioteca COM
Embora o uso de COM o Runtime de simultaneidade segue os mesmos princípios que qualquer outro mecanismo de simultaneidade, as diretrizes a seguir podem ajudá-lo a usar essas bibliotecas juntos com eficiência.
Um segmento deve chamar CoInitializeEx antes que ele usa a biblioteca COM.
Um segmento pode chamar CoInitializeEx várias vezes desde que ele fornece os mesmos argumentos para cada chamada.
Para cada chamada para CoInitializeEx, um thread também deve chamar CoUninitialize. Em outras palavras, chamadas para CoInitializeEx e CoUninitialize deve ser equilibrada.
Para alternar de um thread apartment para outro, um segmento deve liberar completamente a biblioteca COM antes de chamar CoInitializeEx com a nova especificação de threading.
Outros princípios COM se aplicam quando você usa COM o Runtime de simultaneidade. Por exemplo, um aplicativo que cria um objeto em um single-threaded apartment (STA) e empacota desse objeto para outro apartamento também deve fornecer um loop de mensagem para processar mensagens de entrada. Lembre-se também de empacotamento de objetos entre apartments pode diminuir o desempenho.
Usando COM a biblioteca de padrões paralelos
Quando você usa COM um componente na paralela padrões PPL (biblioteca), por exemplo, um grupo de tarefas ou o algoritmo paralelo, chamada CoInitializeEx antes de usar a biblioteca COM durante cada tarefa ou a iteração e a chamada CoUninitialize antes de concluir cada tarefa ou iteração. O exemplo a seguir mostra como gerenciar a vida útil da biblioteca COM um Concurrency::structured_task_group objeto.
structured_task_group tasks;
// Create and run a task.
auto task = make_task([] {
// Initialize the COM library on the current thread.
CoInitializeEx(NULL, COINIT_MULTITHREADED);
// TODO: Perform task here.
// Free the COM library.
CoUninitialize();
});
tasks.run(task);
// TODO: Run additional tasks here.
// Wait for the tasks to finish.
tasks.wait();
Você deve certificar-se de que a biblioteca COM corretamente é liberada quando um algoritmo de tarefa ou em paralelo é cancelado ou quando o corpo da tarefa lança uma exceção. Para garantir que a tarefa chama CoUninitialize antes de ele sai, use um try-finally bloco ou o Inicialização de é de aquisição de recursos padrão de (RAII). O exemplo a seguir utiliza um try-finally bloco para liberar a biblioteca COM quando a tarefa for concluída, ou for cancelada, ou quando uma exceção é lançada.
structured_task_group tasks;
// Create and run a task.
auto task = make_task([] {
bool coinit = false;
__try {
// Initialize the COM library on the current thread.
CoInitializeEx(NULL, COINIT_MULTITHREADED);
coinit = true;
// TODO: Perform task here.
}
__finally {
// Free the COM library.
if (coinit)
CoUninitialize();
}
});
tasks.run(task);
// TODO: Run additional tasks here.
// Wait for the tasks to finish.
tasks.wait();
O exemplo a seguir usa o padrão RAII para definir o CCoInitializer classe, que gerencia o tempo de vida com a biblioteca COM em um determinado escopo.
// An exception-safe wrapper class that manages the lifetime
// of the COM library in a given scope.
class CCoInitializer
{
public:
explicit CCoInitializer(DWORD dwCoInit = COINIT_APARTMENTTHREADED)
: _coinitialized(false)
{
// Initialize the COM library on the current thread.
HRESULT hr = CoInitializeEx(NULL, dwCoInit);
if (FAILED(hr))
throw hr;
_coinitialized = true;
}
~CCoInitializer()
{
// Free the COM library.
if (_coinitialized)
CoUninitialize();
}
private:
// Flags whether COM was properly initialized.
bool _coinitialized;
// Hide copy constructor and assignment operator.
CCoInitializer(const CCoInitializer&);
CCoInitializer& operator=(const CCoInitializer&);
};
Você pode usar o CCoInitializer classe automaticamente liberar a biblioteca COM quando a tarefa é encerrado, como segue:
structured_task_group tasks;
// Create and run a task.
auto task = make_task([] {
// Enable COM for the lifetime of the task.
CCoInitializer coinit(COINIT_MULTITHREADED);
// TODO: Perform task here.
// The CCoInitializer object frees the COM library
// when the task exits.
});
tasks.run(task);
// TODO: Run additional tasks here.
// Wait for the tasks to finish.
tasks.wait();
Para obter mais informações sobre o cancelamento no Runtime de simultaneidade, consulte Cancelamento na PPL.
Usando COM agentes assíncronos
Quando você usa COM agentes assíncronos, chamada CoInitializeEx antes de usar a biblioteca COM na Concurrency::agent::run método para seu agente. Em seguida, chame CoUninitialize antes de run método retorna. Não use as rotinas de gerenciamento COM no construtor ou destrutor de seu agente e não substituem o Concurrency::agent::start ou Concurrency::agent:: feito métodos porque esses métodos são chamados de um thread diferente do run método.
O exemplo a seguir mostra uma classe básica do agente, chamada CCoAgent, que gerencia a biblioteca COM na run método.
class CCoAgent : public agent
{
protected:
void run()
{
// Initialize the COM library on the current thread.
CoInitializeEx(NULL, COINIT_MULTITHREADED);
// TODO: Perform work here.
// Free the COM library.
CoUninitialize();
// Set the agent to the finished state.
done();
}
};
Um exemplo completo é fornecido posteriormente nesta explicação passo a passo.
Usando COM tarefas leve
O documento Agendador de tarefas (Runtime de simultaneidade) descreve a função das tarefas leves no Runtime de simultaneidade. Você pode usar COM uma tarefa simples, como faria com qualquer rotina de thread que você passa para o CreateThread a função no Windows API. Isto é mostrado no exemplo a seguir.
// A basic lightweight task that you schedule directly from a
// Scheduler or ScheduleGroup object.
void ThreadProc(void* data)
{
// Initialize the COM library on the current thread.
CoInitializeEx(NULL, COINIT_MULTITHREADED);
// TODO: Perform work here.
// Free the COM library.
CoUninitialize();
}
Um exemplo de um aplicativo habilitado para COM
Esta seção mostra um aplicativo completo COM habilitado que usa o IScriptControl interface para executar um script que calcula o nth o número de Fibonacci. Este exemplo primeiro chama o script do thread principal e, em seguida, usa a PPL e agentes para chamar o script simultaneamente.
Considere a seguinte função de auxiliar, RunScriptProcedure, que chama um procedimento em um IScriptControl objeto.
// Calls a procedure in an IScriptControl object.
template<size_t ArgCount>
_variant_t RunScriptProcedure(IScriptControlPtr pScriptControl,
_bstr_t& procedureName, array<_variant_t, ArgCount>& arguments)
{
// Create a 1-dimensional, 0-based safe array.
SAFEARRAYBOUND rgsabound[] = { ArgCount, 0 };
CComSafeArray<VARIANT> sa(rgsabound, 1U);
// Copy the arguments to the safe array.
LONG lIndex = 0;
for_each(arguments.begin(), arguments.end(), [&](_variant_t& arg) {
HRESULT hr = sa.SetAt(lIndex, arg);
if (FAILED(hr))
throw hr;
++lIndex;
});
// Call the procedure in the script.
return pScriptControl->Run(procedureName, &sa.m_psa);
}
O wmain função cria uma IScriptControl object, adiciona o código de script a ele que calcula o nth o número de Fibonacci e chama o RunScriptProcedure função para executar esse script.
int wmain()
{
HRESULT hr;
// Enable COM on this thread for the lifetime of the program.
CCoInitializer coinit(COINIT_MULTITHREADED);
// Create the script control.
IScriptControlPtr pScriptControl(__uuidof(ScriptControl));
// Set script control properties.
pScriptControl->Language = "JScript";
pScriptControl->AllowUI = TRUE;
// Add script code that computes the nth Fibonacci number.
hr = pScriptControl->AddCode(
"function fib(n) { if (n<2) return n; else return fib(n-1) + fib(n-2); }" );
if (FAILED(hr))
return hr;
// Test the script control by computing the 15th Fibonacci number.
wcout << endl << L"Main Thread:" << endl;
LONG lValue = 15;
array<_variant_t, 1> args = { _variant_t(lValue) };
_variant_t result = RunScriptProcedure(
pScriptControl,
_bstr_t("fib"),
args);
// Print the result.
wcout << L"fib(" << lValue << L") = " << result.lVal << endl;
return S_OK;
}
Chamando o Script a partir da PPL
A função a seguir, ParallelFibonacci, usa a Concurrency::parallel_for o algoritmo para chamar o script em paralelo. Essa função usa o CCoInitializer classe para gerenciar a vida útil da biblioteca COM durante cada iteração da tarefa.
// Computes multiple Fibonacci numbers in parallel by using
// the parallel_for algorithm.
HRESULT ParallelFibonacci(IScriptControlPtr pScriptControl)
{
try {
parallel_for(10L, 20L, [&pScriptControl](LONG lIndex)
{
// Enable COM for the lifetime of the task.
CCoInitializer coinit(COINIT_MULTITHREADED);
// Call the helper function to run the script procedure.
array<_variant_t, 1> args = { _variant_t(lIndex) };
_variant_t result = RunScriptProcedure(
pScriptControl,
_bstr_t("fib"),
args);
// Print the result.
wstringstream ss;
ss << L"fib(" << lIndex << L") = " << result.lVal << endl;
wcout << ss.str();
});
}
catch (HRESULT hr) {
return hr;
}
return S_OK;
}
Para usar o ParallelFibonacci funcionar com o exemplo, adicione o seguinte código antes do wmain retorna a função.
// Use the parallel_for algorithm to compute multiple
// Fibonacci numbers in parallel.
wcout << endl << L"Parallel Fibonacci:" << endl;
if (FAILED(hr = ParallelFibonacci(pScriptControl)))
return hr;
Chamar o Script de um agente
A exemplo a seguir mostra a FibonacciScriptAgent classe, que chama um procedimento de script para computar o nth o número de Fibonacci. O FibonacciScriptAgent classe usa a mensagem, passando-se para receber, a partir do programa principal, os valores de entrada para a função de script. O run método gerencia o tempo de vida com a biblioteca COM em toda a tarefa.
// A basic agent that calls a script procedure to compute the
// nth Fibonacci number.
class FibonacciScriptAgent : public agent
{
public:
FibonacciScriptAgent(IScriptControlPtr pScriptControl, ISource<LONG>& source)
: _pScriptControl(pScriptControl)
, _source(source) { }
public:
// Retrieves the result code.
HRESULT GetHRESULT()
{
return receive(_result);
}
protected:
void run()
{
// Initialize the COM library on the current thread.
CoInitializeEx(NULL, COINIT_MULTITHREADED);
// Read values from the message buffer until
// we receive the sentinel value.
LONG lValue;
while ((lValue = receive(_source)) != Sentinel)
{
try {
// Call the helper function to run the script procedure.
array<_variant_t, 1> args = { _variant_t(lValue) };
_variant_t result = RunScriptProcedure(
_pScriptControl,
_bstr_t("fib"),
args);
// Print the result.
wstringstream ss;
ss << L"fib(" << lValue << L") = " << result.lVal << endl;
wcout << ss.str();
}
catch (HRESULT hr) {
send(_result, hr);
break;
}
}
// Set the result code (does nothing if a value is already set).
send(_result, S_OK);
// Free the COM library.
CoUninitialize();
// Set the agent to the finished state.
done();
}
public:
// Signals the agent to terminate.
static const LONG Sentinel = 0L;
private:
// The IScriptControl object that contains the script procedure.
IScriptControlPtr _pScriptControl;
// Message buffer from which to read arguments to the
// script procedure.
ISource<LONG>& _source;
// The result code for the overall operation.
single_assignment<HRESULT> _result;
};
A função a seguir, AgentFibonacci, cria várias FibonacciScriptAgent objetos e utiliza passagem de mensagem para enviar vários valores de entrada para esses objetos.
// Computes multiple Fibonacci numbers in parallel by using
// asynchronous agents.
HRESULT AgentFibonacci(IScriptControlPtr pScriptControl)
{
// Message buffer to hold arguments to the script procedure.
unbounded_buffer<LONG> values;
// Create several agents.
array<agent*, 3> agents =
{
new FibonacciScriptAgent(pScriptControl, values),
new FibonacciScriptAgent(pScriptControl, values),
new FibonacciScriptAgent(pScriptControl, values),
};
// Start each agent.
for_each(agents.begin(), agents.end(), [](agent* a) {
a->start();
});
// Send a few values to the agents.
send(values, 30L);
send(values, 22L);
send(values, 10L);
send(values, 12L);
// Send a sentinel value to each agent.
for_each(agents.begin(), agents.end(), [&values](agent* _) {
send(values, FibonacciScriptAgent::Sentinel);
});
// Wait for all agents to finish.
agent::wait_for_all(3, &agents[0]);
// Determine the result code.
HRESULT hr = S_OK;
for_each(agents.begin(), agents.end(), [&hr](agent* a) {
HRESULT hrTemp;
if (FAILED(hrTemp =
reinterpret_cast<FibonacciScriptAgent*>(a)->GetHRESULT()))
{
hr = hrTemp;
}
});
// Clean up.
for_each(agents.begin(), agents.end(), [](agent* a) {
delete a;
});
return hr;
}
Para usar o AgentFibonacci funcionar com o exemplo, adicione o seguinte código antes do wmain retorna a função.
// Use asynchronous agents to compute multiple
// Fibonacci numbers in parallel.
wcout << endl << L"Agent Fibonacci:" << endl;
if (FAILED(hr = AgentFibonacci(pScriptControl)))
return hr;
O exemplo completo
O código a seguir mostra o exemplo completo, que usa algoritmos paralelos e agentes assíncronos para chamar um procedimento de script que calcula os números de Fibonacci.
// parallel-scripts.cpp
// compile with: /EHsc
#include <agents.h>
#include <ppl.h>
#include <array>
#include <sstream>
#include <iostream>
#include <atlsafe.h>
// TODO: Change this path if necessary.
#import "C:\windows\system32\msscript.ocx"
using namespace Concurrency;
using namespace MSScriptControl;
using namespace std;
// An exception-safe wrapper class that manages the lifetime
// of the COM library in a given scope.
class CCoInitializer
{
public:
explicit CCoInitializer(DWORD dwCoInit = COINIT_APARTMENTTHREADED)
: _coinitialized(false)
{
// Initialize the COM library on the current thread.
HRESULT hr = CoInitializeEx(NULL, dwCoInit);
if (FAILED(hr))
throw hr;
_coinitialized = true;
}
~CCoInitializer()
{
// Free the COM library.
if (_coinitialized)
CoUninitialize();
}
private:
// Flags whether COM was properly initialized.
bool _coinitialized;
// Hide copy constructor and assignment operator.
CCoInitializer(const CCoInitializer&);
CCoInitializer& operator=(const CCoInitializer&);
};
// Calls a procedure in an IScriptControl object.
template<size_t ArgCount>
_variant_t RunScriptProcedure(IScriptControlPtr pScriptControl,
_bstr_t& procedureName, array<_variant_t, ArgCount>& arguments)
{
// Create a 1-dimensional, 0-based safe array.
SAFEARRAYBOUND rgsabound[] = { ArgCount, 0 };
CComSafeArray<VARIANT> sa(rgsabound, 1U);
// Copy the arguments to the safe array.
LONG lIndex = 0;
for_each(arguments.begin(), arguments.end(), [&](_variant_t& arg) {
HRESULT hr = sa.SetAt(lIndex, arg);
if (FAILED(hr))
throw hr;
++lIndex;
});
// Call the procedure in the script.
return pScriptControl->Run(procedureName, &sa.m_psa);
}
// Computes multiple Fibonacci numbers in parallel by using
// the parallel_for algorithm.
HRESULT ParallelFibonacci(IScriptControlPtr pScriptControl)
{
try {
parallel_for(10L, 20L, [&pScriptControl](LONG lIndex)
{
// Enable COM for the lifetime of the task.
CCoInitializer coinit(COINIT_MULTITHREADED);
// Call the helper function to run the script procedure.
array<_variant_t, 1> args = { _variant_t(lIndex) };
_variant_t result = RunScriptProcedure(
pScriptControl,
_bstr_t("fib"),
args);
// Print the result.
wstringstream ss;
ss << L"fib(" << lIndex << L") = " << result.lVal << endl;
wcout << ss.str();
});
}
catch (HRESULT hr) {
return hr;
}
return S_OK;
}
// A basic agent that calls a script procedure to compute the
// nth Fibonacci number.
class FibonacciScriptAgent : public agent
{
public:
FibonacciScriptAgent(IScriptControlPtr pScriptControl, ISource<LONG>& source)
: _pScriptControl(pScriptControl)
, _source(source) { }
public:
// Retrieves the result code.
HRESULT GetHRESULT()
{
return receive(_result);
}
protected:
void run()
{
// Initialize the COM library on the current thread.
CoInitializeEx(NULL, COINIT_MULTITHREADED);
// Read values from the message buffer until
// we receive the sentinel value.
LONG lValue;
while ((lValue = receive(_source)) != Sentinel)
{
try {
// Call the helper function to run the script procedure.
array<_variant_t, 1> args = { _variant_t(lValue) };
_variant_t result = RunScriptProcedure(
_pScriptControl,
_bstr_t("fib"),
args);
// Print the result.
wstringstream ss;
ss << L"fib(" << lValue << L") = " << result.lVal << endl;
wcout << ss.str();
}
catch (HRESULT hr) {
send(_result, hr);
break;
}
}
// Set the result code (does nothing if a value is already set).
send(_result, S_OK);
// Free the COM library.
CoUninitialize();
// Set the agent to the finished state.
done();
}
public:
// Signals the agent to terminate.
static const LONG Sentinel = 0L;
private:
// The IScriptControl object that contains the script procedure.
IScriptControlPtr _pScriptControl;
// Message buffer from which to read arguments to the
// script procedure.
ISource<LONG>& _source;
// The result code for the overall operation.
single_assignment<HRESULT> _result;
};
// Computes multiple Fibonacci numbers in parallel by using
// asynchronous agents.
HRESULT AgentFibonacci(IScriptControlPtr pScriptControl)
{
// Message buffer to hold arguments to the script procedure.
unbounded_buffer<LONG> values;
// Create several agents.
array<agent*, 3> agents =
{
new FibonacciScriptAgent(pScriptControl, values),
new FibonacciScriptAgent(pScriptControl, values),
new FibonacciScriptAgent(pScriptControl, values),
};
// Start each agent.
for_each(agents.begin(), agents.end(), [](agent* a) {
a->start();
});
// Send a few values to the agents.
send(values, 30L);
send(values, 22L);
send(values, 10L);
send(values, 12L);
// Send a sentinel value to each agent.
for_each(agents.begin(), agents.end(), [&values](agent* _) {
send(values, FibonacciScriptAgent::Sentinel);
});
// Wait for all agents to finish.
agent::wait_for_all(3, &agents[0]);
// Determine the result code.
HRESULT hr = S_OK;
for_each(agents.begin(), agents.end(), [&hr](agent* a) {
HRESULT hrTemp;
if (FAILED(hrTemp =
reinterpret_cast<FibonacciScriptAgent*>(a)->GetHRESULT()))
{
hr = hrTemp;
}
});
// Clean up.
for_each(agents.begin(), agents.end(), [](agent* a) {
delete a;
});
return hr;
}
int wmain()
{
HRESULT hr;
// Enable COM on this thread for the lifetime of the program.
CCoInitializer coinit(COINIT_MULTITHREADED);
// Create the script control.
IScriptControlPtr pScriptControl(__uuidof(ScriptControl));
// Set script control properties.
pScriptControl->Language = "JScript";
pScriptControl->AllowUI = TRUE;
// Add script code that computes the nth Fibonacci number.
hr = pScriptControl->AddCode(
"function fib(n) { if (n<2) return n; else return fib(n-1) + fib(n-2); }" );
if (FAILED(hr))
return hr;
// Test the script control by computing the 15th Fibonacci number.
wcout << L"Main Thread:" << endl;
long n = 15;
array<_variant_t, 1> args = { _variant_t(n) };
_variant_t result = RunScriptProcedure(
pScriptControl,
_bstr_t("fib"),
args);
// Print the result.
wcout << L"fib(" << n << L") = " << result.lVal << endl;
// Use the parallel_for algorithm to compute multiple
// Fibonacci numbers in parallel.
wcout << endl << L"Parallel Fibonacci:" << endl;
if (FAILED(hr = ParallelFibonacci(pScriptControl)))
return hr;
// Use asynchronous agents to compute multiple
// Fibonacci numbers in parallel.
wcout << endl << L"Agent Fibonacci:" << endl;
if (FAILED(hr = AgentFibonacci(pScriptControl)))
return hr;
return S_OK;
}
O exemplo produz a saída de exemplo a seguir.
Main Thread:
fib(15) = 610
Parallel Fibonacci:
fib(15) = 610
fib(10) = 55
fib(16) = 987
fib(18) = 2584
fib(11) = 89
fib(17) = 1597
fib(19) = 4181
fib(12) = 144
fib(13) = 233
fib(14) = 377
Agent Fibonacci:
fib(30) = 832040
fib(22) = 17711
fib(10) = 55
fib(12) = 144
Compilando o código
Copie o código de exemplo e colá-lo em um Visual Studio do projeto, ou colá-lo em um arquivo que é chamado paralelo scripts.cpp e, em seguida, execute o seguinte comando um Visual Studio 2010 janela do Prompt de comando.
cl.exe /EHsc parallel-scripts.cpp /link ole32.lib
Consulte também
Conceitos
Orientações de Runtime de simultaneidade
Paralelismo de tarefas (Runtime de simultaneidade)