Compartilhar via


Instruções passo a passo: usando o Tempo de Execução de Simultaneidade em um aplicativo habilitado para COM

Este documento demonstra como usar o tempo de execução de simultaneidade em um aplicativo que usa o Component Object Model (COM).

Pré-requisitos

Leia os seguintes documentos antes de iniciar esta explicação passo a passo:

Para obter mais informações sobre como COM o, consulte Component Object Model (COM).

Gerenciando o Tempo de Vida da Biblioteca COM

Embora o uso da com o tempo de execução de simultaneidade siga os mesmos princípios que qualquer outro mecanismo de simultaneidade, as diretrizes a seguir poderão ajudá-lo a usar essas bibliotecas junto com eficiência.

  • Um thread deve chamar CoInitializeEx antes que usa a biblioteca COM.

  • Um thread pode chamar CoInitializeEx várias vezes enquanto fornece os mesmos argumentos a cada chamada.

  • Para cada chamada a CoInitializeEx, um thread também deve chamar CoUninitialize. Ou seja as chamadas a CoInitializeEx e CoUninitialize devem ser equilibrados.

  • Para alternar de um STA. de thread para outro, um thread deve completamente liberar a biblioteca COM antes de chamar CoInitializeEx com a nova especificação de threading.

Outros fundamentos de COM se aplicam quando você usa COM com o tempo de execução de simultaneidade. Por exemplo, um aplicativo que cria um objeto em um STA. de thread único (STA) e em que realiza marshaling objeto para outro STA. também deve fornecer um loop de mensagem para mensagens de entrada do processo. Lembre-se também que o marshaling objetos entre construção pode diminuir o desempenho.

Usar COM com a Biblioteca de Padrões Paralela

Quando você usa COM com um componente em paralelo da biblioteca (PPL), por exemplo, um grupo de trabalho ou um algoritmo paralelo, uma chamada CoInitializeEx antes de usar a biblioteca COM durante cada tarefa ou iteração, e uma chamada CoUninitialize antes de cada tarefa ou de conclusão da iteração. O exemplo a seguir mostra como gerenciar o tempo de vida da biblioteca COM com um objeto de 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();

Você deve ter certeza de que a biblioteca COM é liberada corretamente quando uma tarefa ou um algoritmo paralelo será cancelado ou quando o corpo da tarefa gerencie uma exceção. Para garantir que a tarefa chame CoUninitialize antes de encerrar, use um bloco try-finally ou o padrão Aquisição de recursos é inicialização (RAII). O exemplo a seguir usa um bloco de try-finally para liberar a biblioteca COM quando a tarefa é concluída ou será cancelada, ou quando uma exceção será lançada.

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

O exemplo a seguir usa o padrão de RAII para definir a classe de CCoInitializer , que gerencia o tempo de vida da biblioteca COM em um escopo fornecido.

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

Você pode usar a classe de CCoInitializer para liberar automaticamente como segue a biblioteca COM quando a tarefa é encerrado.

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 obter mais informações sobre de cancelamento em tempo de execução de simultaneidade, consulte Cancelamento no PPL.

Usando COM com Agentes Assíncronos

Quando você usar COM agentes com assíncronas, chame CoInitializeEx antes de usar a biblioteca com no método de concurrency::agent::run para o agente. Na chamada CoUninitialize antes do método de run retorna. Não use rotinas de gerenciamento do no ou no construtor destruidor do agent, e não substituir os métodos de concurrency::agent::start ou de concurrency::agent::done uma vez que esses métodos são chamados de um thread diferente do método de run .

O exemplo a seguir mostra uma classe básica do agent, CCoAgentnomeado, que gerencia a biblioteca COM no método de 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();
   }
};

Um exemplo completo é fornecida posteriormente neste passo a passo.

Usando COM com Tarefas Leves

O documento Agendador de tarefas (Tempo de Execução de Simultaneidade) descreve a função de tarefas de peso leve em tempo de execução de simultaneidade. Você pode usar COM a uma tarefa de peso leve exatamente como você faria com qualquer a rotina de thread que você passar à função de CreateThread na API do windows. Isso é mostrado no exemplo a seguir.

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

Um Exemplo de um Aplicativo Habilitado para COM

Esta seção mostra um aplicativo COM- habilitado completo que usa a interface de IScriptControl para executar um script que calcula o númeroº de Fibonacci em. As chamadas desse exemplo primeiro o script do thread principal, e usar o PPL e os agentes para chamar simultaneamente o script.

Considere a seguinte função auxiliar, RunScriptProcedure, que chama um procedimento em um objeto de 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);
}

A função de wmain cria um objeto de IScriptControl , adicionar o código de script que computa oº número de Fibonacci em, e chame a função de RunScriptProcedure para executar esse 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;
}

Chamando o Script da PPL

A seguinte função, ParallelFibonacci, o usa o algoritmo de concurrency::parallel_for para chamar em paralelo o script. Essa função usa a classe de CCoInitializer para gerenciar o tempo de vida da biblioteca COM durante cada iteração da tarefa.

// 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 a função de ParallelFibonacci com o exemplo, adicione o seguinte código antes que a função de wmain retorna.

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

Chamando o Script de um Agente

O exemplo a seguir mostra a classe de FibonacciScriptAgent , que chama um procedimento de script para calcular o númeroº de Fibonacci em. A classe de FibonacciScriptAgent usa a mensagem que transmite para receber, o programa principal, valores de entrada à função de script. O método de run gerencia o tempo de vida da biblioteca COM durante toda a tarefa.

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

A seguinte função, AgentFibonacci, criar vários objetos de FibonacciScriptAgent e usa a mensagem que transmite para enviar vários valores de entrada 2 esses 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(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;
}

Para usar a função de AgentFibonacci com o exemplo, adicione o seguinte código antes que a função de wmain retorna.

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

O Exemplo Completo

O código a seguir mostra o exemplo completo, que usa algoritmos paralelos e agentes assíncronas para chamar um procedimento de script que calcula 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(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;
}

O exemplo gerencia a seguinte saída de exemplo.

  

Compilando o código

Copie o código de exemplo e cole-o em um projeto do Visual Studio, ou cole-o em um arquivo chamado parallel-scripts.cpp e execute o comando a seguir em uma janela de prompt de comando do Visual Studio.

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

Consulte também

Conceitos

Paralelismo de tarefa (tempo de execução de simultaneidade)

Algoritmos paralelos

Agentes assíncronos

Tratamento de exceções no tempo de execução de simultaneidade

Cancelamento no PPL

Agendador de tarefas (Tempo de Execução de Simultaneidade)

Outros recursos

Instruções passo a passo do Tempo de Execução de Simultaneidade