연습: COM 사용 애플리케이션에서 동시성 런타임 사용
이 문서에서는 COM(구성 요소 개체 모델)을 사용하는 애플리케이션에서 동시성 런타임을 사용하는 방법을 보여 줍니다.
필수 조건
이 연습을 시작하기 전에 다음 문서를 읽어보세요.
COM에 대한 자세한 내용은 COM(구성 요소 개체 모델)을 참조하세요.
COM 라이브러리의 수명 관리
동시성 런타임에서 COM을 사용하는 것은 다른 동시성 메커니즘과 동일한 원칙을 따르지만, 다음 지침은 이러한 라이브러리를 효과적으로 함께 사용하는 데 도움이 될 수 있습니다.
스레드는 COM 라이브러리를 사용하기 전에 CoInitializeEx를 호출해야 합니다.
스레드는 모든 호출에 동일한 인수를 제공하는 한 여러 번 호출
CoInitializeEx
할 수 있습니다.각 호출에
CoInitializeEx
대해 스레드는 CoUninitialize를 호출해야 합니다. 즉, 호출 및CoInitializeEx
CoUninitialize
균형을 유지해야 합니다.스레드 아파트에서 다른 스레드 아파트로 전환하려면 스레드가 새 스레딩 사양을 사용하여 호출
CoInitializeEx
하기 전에 COM 라이브러리를 완전히 해제해야 합니다.
다른 COM 원칙은 동시성 런타임에서 COM을 사용할 때 적용됩니다. 예를 들어 STA(단일 스레드 아파트)에 개체를 만들고 해당 개체를 다른 아파트로 마샬링하는 애플리케이션은 들어오는 메시지를 처리하는 메시지 루프를 제공해야 합니다. 또한 아파트 간에 개체를 마샬링하면 성능이 저하됩니다.
병렬 패턴 라이브러리와 함께 COM 사용
PPL(병렬 패턴 라이브러리)의 구성 요소와 함께 COM을 사용하는 경우(예: 작업 그룹 또는 병렬 알고리즘) 각 작업 또는 반복 중에 COM 라이브러리를 사용하기 전에 호출 CoInitializeEx
하고 각 작업 또는 반복이 완료되기 전에 호출 CoUninitialize
합니다. 다음 예제에서는 동시성::structured_task_group 개체를 사용하여 COM 라이브러리의 수명을 관리하는 방법을 보여줍니다.
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();
작업 또는 병렬 알고리즘이 취소되거나 작업 본문이 예외를 throw할 때 COM 라이브러리가 올바르게 해제되었는지 확인해야 합니다. 태스크가 종료되기 전에 호출 CoUninitialize
되도록 하려면 블록 또는 RAII(리소스 취득 초기화) 패턴을 사용합니다try-finally
. 다음 예제에서는 작업이 완료되거나 취소되거나 예외가 throw될 때 블록을 사용하여 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 패턴을 사용하여 지정된 범위에서 COM 라이브러리의 수명을 관리하는 클래스를 정의 CCoInitializer
합니다.
// 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을 사용하는 경우 에이전트에 대한 동시성::agent::run 메서드에서 COM 라이브러리를 사용하기 전에 호출 CoInitializeEx
합니다. 그런 다음 메서드가 반환되기 전에 호출 CoUninitialize
합니다 run
. 에이전트의 생성자 또는 소멸자에서 COM 관리 루틴을 사용하지 말고, 이러한 메서드가 메서드와 다른 스레드에서 호출되므로 동시성::agent::start 또는 concurrency::agent::d one 메서드를 재정의 run
하지 마세요.
다음 예제에서는 메서드에서 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 사용
작업 스케줄러 문서에서는 동시성 런타임에서 간단한 작업의 역할을 설명합니다. Windows API의 함수에 전달하는 CreateThread
스레드 루틴과 마찬가지로 간단한 작업으로 COM을 사용할 수 있습니다. 이 방법은 다음 예제에서 확인할 수 있습니다.
// 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 지원 애플리케이션의 예
이 섹션에서는 인터페이스를 사용하여 IScriptControl
n번째 피보나치 번호를 계산하는 스크립트를 실행하는 완전한 COM 지원 애플리케이션을 보여 줍니다. 이 예제에서는 먼저 주 스레드에서 스크립트를 호출한 다음 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
만들고 n번째 피보나치 번호를 계산하는스크립트 코드를 추가한 다음 함수를 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
는 concurrency::p arallel_for 알고리즘을 사용하여 스크립트를 병렬로 호출합니다. 이 함수는 클래스를 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;
에이전트에서 스크립트 호출
다음 예제에서는 n번째 피보나치 번호를 계산하는 스크립트 프로시저를 호출하는 클래스를 보여 FibonacciScriptAgent
있습니다. 클래스는 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
참고 항목
동시성 런타임 연습
작업 병렬 처리
병렬 알고리즘
비동기 에이전트
예외 처리
PPL에서의 취소
작업 Scheduler