Пошаговое руководство. Использование среды выполнения с параллелизмом в приложениях с поддержкой модели COM
В этом документе показано, как использовать среду выполнения параллелизма в приложении, использующее объектную модель компонента (COM).
Необходимые компоненты
Ознакомьтесь со следующими документами перед началом работы с этим пошаговом руководстве.
Дополнительные сведения о COM см. в разделе "Объектная модель компонентов" (COM).
Управление временем существования библиотеки COM
Хотя использование COM с средой выполнения параллелизма соответствует тем же принципам, что и любой другой механизм параллелизма, приведенные ниже рекомендации помогут эффективно использовать эти библиотеки вместе.
Перед использованием библиотеки COM поток должен вызывать CoInitializeEx .
Поток может вызывать
CoInitializeEx
несколько раз, пока он предоставляет одинаковые аргументы для каждого вызова.Для каждого вызова
CoInitializeEx
поток должен также вызывать CoUninitialize. Другими словами, вызовыCoInitializeEx
иCoUninitialize
должны быть сбалансированы.Чтобы переключиться с одной квартиры потоков на другую, поток должен полностью освободить com-библиотеку перед вызовом
CoInitializeEx
новой спецификации потоков.
Другие принципы COM применяются при использовании COM с средой выполнения параллелизма. Например, приложение, создающее объект в однопоточной квартире (STA) и маршалы, которые объекту в другую квартиру, также должны предоставить цикл сообщений для обработки входящих сообщений. Также помните, что маршалинг объектов между квартирами может снизить производительность.
Использование COM с библиотекой параллельных шаблонов
Если вы используете COM с компонентом в библиотеке параллельных шаблонов (PPL), например, группу задач или параллельный алгоритм, вызовите CoInitializeEx
перед использованием com-библиотеки во время каждой задачи или итерации и вызов CoUninitialize
перед завершением каждой задачи или итерации. В следующем примере показано, как управлять временем существования библиотеки COM с помощью объекта concurrency::structured_task_group .
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();
Необходимо убедиться, что com-библиотека освобождена правильно при отмене задачи или параллельного алгоритма или при возникновении исключения в теле задачи. Чтобы гарантировать, что задача вызывается CoUninitialize
перед выходом try-finally
, используйте блок или шаблон инициализации ресурсов (RAII). В следующем примере используется try-finally
блок для освобождения com-библиотеки при завершении или отмене задачи или при возникновении исключения.
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();
В следующем примере используется шаблон RAII для определения CCoInitializer
класса, который управляет временем существования библиотеки COM в заданной области.
// 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 (SUCCEEDED(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&);
};
Класс можно использовать CCoInitializer
для автоматического освобождения com-библиотеки при выходе задачи, как показано ниже.
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();
Дополнительные сведения об отмене в среде выполнения параллелизма см. в разделе "Отмена" в PPL.
Использование COM с асинхронными агентами
При использовании COM с асинхронными агентами вызовите CoInitializeEx
перед использованием библиотеки COM в методе concurrency::agent::run для агента. Затем вызовите CoUninitialize
перед возвратом run
метода. Не используйте процедуры управления COM в конструкторе или деструкторе агента и не переопределяете методы параллелизма::agent:start или concurrency::agent::d one , так как эти методы вызываются из другого потока run
.
В следующем примере показан базовый класс агента с именем CCoAgent
, который управляет библиотекой COM в методе run
.
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();
}
};
Полный пример представлен далее в этом пошаговом руководстве.
Использование COM с упрощенными задачами
Планировщик задач документа описывает роль упрощенных задач в среде выполнения параллелизма. Com можно использовать с упрощенной задачей так же, как и с любой подпрограммой потока, передаваемой CreateThread
в функцию в API Windows. Это показано в следующем примере.
// 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();
}
Пример приложения с поддержкой COM
В этом разделе показано полное приложение с поддержкой COM, использующее IScriptControl
интерфейс для выполнения скрипта, который вычисляет n-й номер Fibonacci. В этом примере сначала вызывается скрипт из основного потока, а затем используется PPL и агенты для одновременного вызова скрипта.
Рассмотрим следующую вспомогающую функцию, RunScriptProcedure
которая вызывает процедуру в объекте IScriptControl
.
// 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(begin(arguments), end(arguments), [&](_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);
}
Функция wmain
создает IScriptControl
объект, добавляет в него код скрипта, вычисляющий nth Fibonacci number, а затем вызывает RunScriptProcedure
функцию для запуска этого скрипта.
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;
}
Вызов скрипта из PPL
Следующая функция ParallelFibonacci
использует параллелизм::p arallel_для параллельного вызова скрипта. Эта функция использует CCoInitializer
класс для управления временем существования библиотеки COM во время каждой итерации задачи.
// 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;
}
Чтобы использовать ParallelFibonacci
функцию с примером, добавьте следующий код перед возвратом wmain
функции.
// 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;
Вызов скрипта из агента
В следующем примере показан FibonacciScriptAgent
класс, который вызывает процедуру скрипта для вычисления n-го номера Fibonacci. Класс FibonacciScriptAgent
использует передачу сообщений для получения из основной программы входных значений в функцию скрипта. Метод run
управляет временем существования библиотеки COM во всей задаче.
// 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;
};
Следующая функция AgentFibonacci
создает несколько FibonacciScriptAgent
объектов и использует передачу сообщений для отправки нескольких входных значений этим объектам.
// 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(begin(agents), end(agents), [](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(begin(agents), end(agents), [&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(begin(agents), end(agents), [&hr](agent* a) {
HRESULT hrTemp;
if (FAILED(hrTemp =
reinterpret_cast<FibonacciScriptAgent*>(a)->GetHRESULT()))
{
hr = hrTemp;
}
});
// Clean up.
for_each(begin(agents), end(agents), [](agent* a) {
delete a;
});
return hr;
}
Чтобы использовать AgentFibonacci
функцию с примером, добавьте следующий код перед возвратом wmain
функции.
// Use asynchronous agents to compute multiple
// Fibonacci numbers in parallel.
wcout << endl << L"Agent Fibonacci:" << endl;
if (FAILED(hr = AgentFibonacci(pScriptControl)))
return hr;
Полный пример
В следующем коде показан полный пример, который использует параллельные алгоритмы и асинхронные агенты для вызова процедуры скрипта, которая вычисляет номера 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(begin(arguments), end(arguments), [&](_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(begin(agents), end(agents), [](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(begin(agents), end(agents), [&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(begin(agents), end(agents), [&hr](agent* a) {
HRESULT hrTemp;
if (FAILED(hrTemp =
reinterpret_cast<FibonacciScriptAgent*>(a)->GetHRESULT()))
{
hr = hrTemp;
}
});
// Clean up.
for_each(begin(agents), end(agents), [](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;
}
В примере приводится следующий пример выходных данных.
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
Компиляция кода
Скопируйте пример кода и вставьте его в проект Visual Studio или вставьте его в файл с именем parallel-scripts.cpp
, а затем выполните следующую команду в окне командной строки Visual Studio.
cl.exe /EHsc parallel-scripts.cpp /link ole32.lib
См. также
Пошаговые руководства по среде выполнения с параллелизмом
Параллелизм задач
Параллельные алгоритмы
Асинхронные агенты
Обработка исключений
Отмена в библиотеке параллельных шаблонов
Планировщик заданий