Návod: Použití Concurrency Runtime v aplikaci s podporou modelu COM
Tento dokument ukazuje použití modulu Concurrency Runtime v aplikaci, která používá model COM (Component Object Model).
Požadavky
Před zahájením tohoto návodu si přečtěte následující dokumenty:
Další informace o modelu COM naleznete v tématu Model com (Component Object Model).
Správa životního cyklu knihovny modelu COM
I když použití modelu COM s modulem Concurrency Runtime se řídí stejnými principy jako jakýkoli jiný mechanismus souběžnosti, následující pokyny vám můžou pomoct tyto knihovny efektivně používat společně.
Vlákno musí před použitím knihovny COM volat CoInitializeEx .
Vlákno může volat
CoInitializeEx
vícekrát, dokud každému volání poskytuje stejné argumenty.Pro každé volání
CoInitializeEx
musí vlákno také volat CoUninitialize. Jinými slovy, volání aCoInitializeEx
CoUninitialize
musí být vyvážená.Chcete-li přepnout z jednoho vlákna na druhý, vlákno musí zcela uvolnit knihovnu COM před voláním
CoInitializeEx
s novou specifikací vláken.
Jiné principy modelu COM platí při použití modelu COM s modulem Concurrency Runtime. Například aplikace, která vytvoří objekt v jednovláknovém bytě (STA) a zařadí tento objekt do jiného bytu, musí také poskytnout smyčku zpráv pro zpracování příchozích zpráv. Nezapomeňte také, že zařazování objektů mezi apartmány může snížit výkon.
Používání modelu COM s knihovnou PPL
Při použití modelu COM s komponentou v knihovně PPL (Parallel Patterns Library), například skupině úloh nebo paralelním algoritmus, volání CoInitializeEx
před použitím knihovny COM během každého úkolu nebo iterace a volání CoUninitialize
před dokončením každého úkolu nebo iterace. Následující příklad ukazuje, jak spravovat životnost knihovny COM pomocí concurrency::structured_task_group objektu.
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();
Musíte se ujistit, že je knihovna COM správně uvolněna, pokud je zrušen úkol nebo paralelní algoritmus nebo když tělo úkolu vyvolá výjimku. Chcete-li zaručit, že úloha volá CoUninitialize
před ukončením, použijte try-finally
blok nebo model RAII (Resource Acquisition Is Initialization ). Následující příklad používá try-finally
blok k uvolnění knihovny COM při dokončení nebo zrušení úkolu nebo při vyvolání výjimky.
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();
Následující příklad používá vzor RAII k definování CCoInitializer
třídy, která spravuje životnost knihovny COM v daném oboru.
// 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&);
};
Třídu CCoInitializer
můžete použít k automatickému uvolnění knihovny COM při ukončení úkolu následujícím způsobem.
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();
Další informace o zrušení v modulu Concurrency Runtime naleznete v tématu Zrušení v PPL.
Používání modelu COM s asynchronními agenty
Při použití modelu COM s asynchronními agenty zavolejte CoInitializeEx
před použitím knihovny COM v metodě concurrency::agent::run pro vašeho agenta. Potom zavolejte CoUninitialize
před vrácením run
metody. Nepoužívejte rutiny správy modelu COM v konstruktoru nebo destruktoru vašeho agenta a nepřepište souběžnost::agent::start nebo concurrency::agent::agent::d one metody, protože tyto metody jsou volány z jiného vlákna než metoda run
.
Následující příklad ukazuje základní třídu agenta s názvem CCoAgent
, která spravuje knihovnu run
COM v metodě.
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();
}
};
Úplný příklad je k dispozici dále v tomto názorném postupu.
Používání modelu COM s prostými úlohami
Plánovač úloh dokumentu popisuje roli jednoduchých úloh v modulu Concurrency Runtime. Com můžete použít s jednoduchou úlohou stejně jako u jakékoli rutiny vlákna, kterou předáte CreateThread
funkci v rozhraní API systému Windows. To je ukázáno v následujícím příkladu.
// 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();
}
Příklad aplikace s podporou modelu COM
Tato část ukazuje kompletní aplikaci s podporou modelu COM, která používá IScriptControl
rozhraní ke spuštění skriptu, který vypočítá nfibonacciho číslo. Tento příklad nejprve volá skript z hlavního vlákna a pak používá PPL a agenty k souběžnému volání skriptu.
Zvažte následující pomocnou funkci, RunScriptProcedure
která volá proceduru v objektu 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);
}
Funkce wmain
vytvoří IScriptControl
objekt, přidá do něj kód skriptu, který vypočítá nfibonacciho číslo a potom zavolá RunScriptProcedure
funkci ke spuštění daného skriptu.
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;
}
Volání skriptu z knihovny PPL
Následující funkce ParallelFibonacci
používá souběžnost::p arallel_for algoritmus k paralelnímu volání skriptu. Tato funkce používá CCoInitializer
třídu ke správě životnosti knihovny COM během každé iterace úlohy.
// 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;
}
Chcete-li použít ParallelFibonacci
funkci s příkladem, přidejte následující kód před wmain
vrácení funkce.
// 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;
Volání skriptu z agenta
Následující příklad ukazuje FibonacciScriptAgent
třídu, která volá proceduru skriptu pro výpočet nfibonacciho čísla. Třída FibonacciScriptAgent
používá předávání zpráv pro příjem vstupních hodnot z hlavního programu do funkce skriptu. Tato run
metoda spravuje životnost knihovny MODELU COM v průběhu úkolu.
// 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;
};
Následující funkce vytvoří AgentFibonacci
několik FibonacciScriptAgent
objektů a používá předávání zpráv k odeslání několika vstupních hodnot těmto objektům.
// 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;
}
Chcete-li použít AgentFibonacci
funkci s příkladem, přidejte následující kód před wmain
vrácení funkce.
// Use asynchronous agents to compute multiple
// Fibonacci numbers in parallel.
wcout << endl << L"Agent Fibonacci:" << endl;
if (FAILED(hr = AgentFibonacci(pScriptControl)))
return hr;
Kompletní příklad
Následující kód ukazuje úplný příklad, který používá paralelní algoritmy a asynchronní agenty k volání procedury skriptu, která vypočítá Fibonacci čísla.
// 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;
}
Příklad vytvoří následující ukázkový výstup.
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
Probíhá kompilace kódu
Zkopírujte ukázkový kód a vložte ho do projektu sady Visual Studio nebo ho vložte do pojmenovaného parallel-scripts.cpp
souboru a potom v okně příkazového řádku sady Visual Studio spusťte následující příkaz.
cl.exe /EHsc parallel-scripts.cpp /link ole32.lib
Viz také
Návody pro Concurrency Runtime
Paralelismus úkolu
Paralelní algoritmy
Asynchronní agenti
Zpracování výjimek
Zrušení v knihovně PPL
Plánovač úloh