Tutorial: Usar el Runtime de simultaneidad en una aplicación habilitada para COM

En este documento se muestra cómo se usa el Runtime de simultaneidad en una aplicación que emplea el Modelo de objetos componentes (COM).

Requisitos previos

Lea los documentos siguientes antes de iniciar este tutorial:

Para obtener más información sobre COM, vea Component Object Model (COM).

Administrar la duración de la biblioteca COM

Aunque el uso de COM con el Runtime de simultaneidad sigue los mismos principios que cualquier otro mecanismo de simultaneidad, las siguientes instrucciones pueden ayudarle a usar conjuntamente estas bibliotecas de forma eficaz.

  • Un subproceso debe llamar a CoInitializeEx antes de usar la biblioteca COM.

  • Un subproceso puede llamar varias veces a CoInitializeEx siempre que proporcione los mismos argumentos en cada llamada.

  • En cada llamada a CoInitializeEx, debe haber un subproceso que llame también a CoUninitialize. En otras palabras, las llamadas a CoInitializeEx y CoUninitialize deben estar equilibradas.

  • Para pasar de un apartamento de subproceso a otro, un subproceso debe liberar completamente la biblioteca COM antes de llamar a CoInitializeEx con la nueva especificación de subprocesamiento.

Cuando se usa COM con el runtime de simultaneidad, se aplican otros principios de COM. Por ejemplo, una aplicación que crea un objeto en un apartamento de un único subproceso (STA) y calcula las referencias de ese objeto en otro apartamento también debe proporcionar un bucle de mensajes para procesar los mensajes entrantes. Recuerde también que el cálculo de referencias de objetos entre apartamentos puede reducir el rendimiento.

Usar COM con la Biblioteca de modelos paralelos

Cuando usa COM con un componente de la Biblioteca de modelos paralelos (PPL), por ejemplo, un algoritmo paralelo o un grupo de tareas llama a CoInitializeEx antes de usar la biblioteca COM durante cada tarea o iteración y llama a CoUninitialize antes de que finalice cada tarea o iteración. En el ejemplo siguiente se muestra cómo administrar la duración de la biblioteca COM con un objeto 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();

Debe asegurarse de que la biblioteca COM se libera correctamente cuando se cancela una tarea o algoritmo paralelo, o cuando el cuerpo de la tarea produce una excepción. Para garantizar que la tarea llama a CoUninitialize antes de salir, use un bloque try-finally o el modelo Resource Acquisition Is Initialization (RAII). En el ejemplo siguiente se usa un bloque try-finally para liberar la biblioteca COM cuando la tarea se completa o se cancela, o cuando se produce una excepción.

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

En el ejemplo siguiente se usa un modelo RAII para definir la clase CCoInitializer, que administra la duración de la biblioteca COM en un ámbito determinado.

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

Puede usar la clase CCoInitializer para liberar la biblioteca COM automáticamente cuando la tarea finaliza, tal y como se muestra a continuación.

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

Para obtener más información sobre la cancelación del Runtime de simultaneidad, vea Cancelación en la biblioteca PPL.

Usar COM con agentes asincrónicos

Cuando use COM con agentes asincrónicos, llame a CoInitializeEx antes de usar la biblioteca COM en el método Concurrency::agent::run del agente. A continuación, llame a CoUninitialize antes de que el método run devuelva un resultado. No use las rutinas de administración COM en el constructor o destructor del agente y no reemplace los métodos Concurrency::agent::start ni Concurrency::agent::done porque a estos métodos se les llama desde un subproceso distinto al del método run.

En el siguiente ejemplo se muestra una clase de agente básica, denominada CCoAgent, que administra la biblioteca COM en el método 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();
   }
};

Más adelante en este tutorial se proporciona un ejemplo completo.

Usar COM con tareas ligeras

En el documento Programador de tareas (Runtime de simultaneidad) se describe el rol de las tareas ligeras en el Runtime de simultaneidad. Puede usar COM con una tarea ligera del mismo modo que lo haría con cualquier rutina de subprocesamiento que pase a la función CreateThread de la API de Windows. Esta implementación se muestra en el ejemplo siguiente.

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

Ejemplo de una aplicación habilitada para COM

En esta sección se muestra una aplicación completa habilitada para COM que usa la interfaz IScriptControl para ejecutar un script que calcula el enésimo número de Fibonacci. En este ejemplo se llama primero al script desde el subproceso principal y, a continuación, se usa la biblioteca PPL y los agentes para llamar al script simultáneamente.

Considere la siguiente función auxiliar, RunScriptProcedure, que llama a un procedimiento en un objeto 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);
}

La función wmain crea un objeto IScriptControl, le agrega el código del script que calcula el enésimo número de Fibonacci y, a continuación, llama a la función RunScriptProcedure para ejecutar ese script.

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

Llamar al script desde la biblioteca PPL

La siguiente función, ParallelFibonacci, usa el algoritmo Concurrency::parallel_for para llamar al script en paralelo. Esta función usa la clase CCoInitializer para administrar la duración de la biblioteca COM durante cada iteración de la tarea.

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

Para usar la función ParallelFibonacci con el ejemplo, agregue el código siguiente antes de que la función wmain devuelva un resultado.

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

Llamar al script desde un agente

En el ejemplo siguiente se muestra la clase FibonacciScriptAgent, que llama a un procedimiento de script para calcular el enésimo número de Fibonacci. La clase FibonacciScriptAgent usa el paso de mensajes para recibir del programa principal los valores de entrada para la función del script. El método run administra la duración de la biblioteca COM a lo largo de la tarea.

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

La siguiente función, AgentFibonacci, crea varios objetos FibonacciScriptAgent y usa el paso de mensajes para enviar varios valores de entrada a esos objetos.

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

Para usar la función AgentFibonacci con el ejemplo, agregue el código siguiente antes de que la función wmain devuelva un resultado.

// Use asynchronous agents to compute multiple 
// Fibonacci numbers in parallel.
wcout << endl << L"Agent Fibonacci:" << endl;
if (FAILED(hr = AgentFibonacci(pScriptControl)))
   return hr;

Ejemplo completo

En el código siguiente se muestra el ejemplo completo, en el que se usan algoritmos paralelos y agentes asincrónicos para llamar a un procedimiento de script que calcula los números de 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(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;
}

El ejemplo genera la siguiente salida de ejemplo:

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

Compilar el código

Copie el código de ejemplo y péguelo en un proyecto de Visual Studio o péguelo en un archivo denominado parallel-scripts.cpp y, a continuación, ejecute el siguiente comando en una ventana del símbolo del sistema de Visual Studio 2010.

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

Vea también

Conceptos

Tutoriales del Runtime de simultaneidad

Paralelismo de tareas (Runtime de simultaneidad)

Algoritmos paralelos

Agentes asincrónicos

Control de excepciones en el runtime de simultaneidad

Cancelación en la biblioteca PPL

Programador de tareas (Runtime de simultaneidad)