Поделиться через


Пошаговое руководство. Использование среды выполнения с параллелизмом в приложениях с поддержкой модели COM

В этом документе показывается, как использовать среду выполнения с параллелизмом в приложении, использующем модель COM.

Обязательные компоненты

Прежде чем начать выполнение этого пошагового руководства, необходимо ознакомиться со следующими документами.

Дополнительные сведения о COM см. в разделе Модель COM.

Управление временем существования библиотеки COM

Несмотря на то, что использование COM совместно со средой выполнения с параллелизмом базируется на тех же принципах, что и любой другой механизм параллелизма, следующие рекомендации могут помочь в эффективном совместном использовании этих библиотек.

  • Поток должен вызвать метод CoInitializeEx перед использованием библиотеки COM.

  • Поток может вызывать метод CoInitializeEx несколько раз, если он предоставляет при вызовах одинаковые аргументы.

  • При каждом вызове метода CoInitializeEx поток должен также выполнить вызов метода CoUninitialize. Другими словами, вызовы методов CoInitializeEx и CoUninitialize должны быть сбалансированы.

  • При переключении от одного потокового подразделения к другому поток должен полностью освободить библиотеку COM перед вызовом метода CoInitializeEx с новой потоковой спецификацией.

При использовании модели COM со средой выполнения с параллелизмом применяются и другие правила COM. Например, приложение, создающее объект в однопотоковом подразделении (STA) и маршалирующее его в другое подразделение, должно также предоставить цикл сообщений для обработки входящих сообщений. Также следует помнить, что маршалирование объектов между подразделениями может снизить производительность.

Использование COM с библиотекой параллельных шаблонов

При использовании COM в библиотеке параллельных шаблонов, например, с компонентом группы задач или параллельного алгоритма, следует вызвать метод 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 (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&);
};

Можно воспользоваться классом 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();

Дополнительные сведения об отмене в среде выполнения с параллелизмом см. в разделе Отмена в библиотеке параллельных шаблонов.

Использование COM с асинхронными агентами

При использовании COM с асинхронными агентами следует вызывать метод CoInitializeEx до использования библиотеки COM в методе Concurrency::agent::run для вашего агента. Затем необходимо вызвать метод CoUninitialize перед возвратом метода run. Не используйте подпрограммы управления COM в конструкторе или деструкторе агента, не переопределяйте методы Concurrency::agent::start или Concurrency::agent::done, так как эти методы вызываются из потока, отличного от потока, из которого вызывается метод 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 c упрощенными задачами

Документ Планировщик задач (среда выполнения с параллелизмом) описывает роль упрощенных задач в среде выполнения с параллелизмом. Можно использовать модель 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-ный член последовательности Фибоначчи. Этот пример сначала вызывает скрипт из главного потока, а затем использует 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(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;

Вызов скрипта из агента

Следующий пример показывает класс FibonacciScriptAgent, вызывающий процедуру скрипта для расчета n-ного члена последовательности Фибоначчи. Класс 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-array-search.cpp, затем выполните в окне командной строки Visual Studio 2010 следующую команду.

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

См. также

Основные понятия

Пошаговые руководства по среде выполнения с параллелизмом

Параллелизм задач (среда выполнения с параллелизмом)

Параллельные алгоритмы

Асинхронные агенты

Обработка исключений в среде выполнения с параллелизмом

Отмена в библиотеке параллельных шаблонов

Планировщик задач (среда выполнения с параллелизмом)