다음을 통해 공유


연습: COM 사용 응용 프로그램에서 동시성 런타임 사용

이 문서에서는 COM(Component Object Model)을 사용하는 응용 프로그램에서 동시성 런타임을 활용하는 방법을 보여 줍니다.

사전 요구 사항

이 연습을 시작하기 전에 다음 문서를 읽어 보십시오.

COM에 대한 자세한 내용은 COM(Component Object Model)을 참조하십시오.

COM 라이브러리의 수명 관리

동시성 런타임과 함께 COM을 사용할 경우 다른 동시성 메커니즘과 동일한 원칙을 따르게 되지만 다음 지침을 참고하면 이러한 라이브러리를 효율적으로 같이 사용하는 데 도움이 됩니다.

  • 스레드는 COM 라이브러리를 사용하기 전에 CoInitializeEx를 호출해야 합니다.

  • 스레드는 호출할 때마다 동일한 인수를 제공하기만 하면 CoInitializeEx를 여러 번 호출할 수 있습니다.

  • CoInitializeEx를 호출할 때마다 스레드는 CoUninitialize도 호출해야 합니다. 즉, CoInitializeExCoUninitialize에 대한 호출이 균형을 이루어야 합니다.

  • 특정 스레드 아파트에서 다른 아파트로 전환하려면 스레드가 새 스레딩 사양에 따라 CoInitializeEx를 호출하기 전에 COM 라이브러리를 완전히 해제해야 합니다.

동시성 런타임과 함께 COM을 사용하는 경우 다른 COM 원칙이 적용됩니다. 예를 들어 STA(단일 스레드 아파트)에서 개체를 만들고 해당 개체를 다른 아파트로 마샬링하는 응용 프로그램에서는 들어오는 메시지를 처리할 메시지 루프도 제공해야 합니다. 아파트 간에 개체를 마샬링하면 성능이 저하될 수도 있습니다.

병렬 패턴 라이브러리와 함께 COM 사용

PPL(병렬 패턴 라이브러리)의 구성 요소(예: 작업 그룹 또는 병렬 알고리즘)와 함께 COM을 사용하는 경우 각 작업 또는 반복 도중 COM 라이브러리를 사용하기 전에 CoInitializeEx를 호출하고, 각 작업 또는 반복 완료 전에 CoUninitialize를 호출합니다. 다음 예제에서는 Concurrency::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를 호출하도록 하려면 try-finally 블록 또는 RAII(Resource Acquisition Is Initialization) 패턴을 사용합니다. 다음 예제에서는 작업이 완료되거나 취소되는 경우 또는 예외가 되는 경우에 COM 라이브러리를 해제하기 위해 try-finally 블록을 사용합니다.

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();

다음 예제에서는 해당 범위에서 COM 라이브러리의 수명을 관리하는 CCoInitializer 클래스를 정의하기 위해 RAII 패턴을 사용합니다.

// 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&);
};

작업 종료 시 COM 라이브러리가 자동으로 해제되도록 하기 위해 다음과 같이 CCoInitializer 클래스를 사용할 수 있습니다.

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을 사용하는 경우 에이전트의 Concurrency::agent::run 메서드에서 COM 라이브러리를 사용하기 전에 CoInitializeEx를 호출합니다. 그런 다음 run 메서드가 반환하기 전에 CoUninitialize를 호출합니다. COM 관리 루틴을 에이전트의 생성자 또는 소멸자에서 사용하지 마십시오. 또한 Concurrency::agent::startConcurrency::agent::done 메서드는 run 메서드가 아닌 다른 스레드에서 호출되므로 이러한 메서드를 재정의하지 마십시오.

다음 예제에서는 CCoAgent라는 기본 에이전트 클래스를 보여 줍니다. 이 클래스는 run 메서드에서 COM 라이브러리를 관리합니다.

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과 에이전트를 사용하여 스크립트를 동시에 호출합니다.

IScriptControl 개체에 있는 프로시저를 호출하는 다음 도우미 함수 RunScriptProcedure를 살펴봅니다.

// 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);
}

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::parallel_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(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;
}

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;

전체 예제

다음 코드는 병렬 알고리즘 및 비동기 에이전트를 사용하여 피보나치 수를 계산하는 스크립트 프로시저를 호출하는 예제를 전체적으로 보여 줍니다.

// 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;
}

이 예제를 실행하면 다음과 같은 샘플 결과가 출력됩니다.

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 2010 명령 프롬프트 창에서 다음 명령을 실행합니다.

cl.exe /EHsc parallel-scripts.cpp /link ole32.lib

참고 항목

개념

동시성 런타임 연습

작업 병렬 처리(동시성 런타임)

병렬 알고리즘

비동기 에이전트

동시성 런타임에서 예외 처리

PPL에서의 취소

작업 스케줄러(동시성 런타임)