Compartilhar via


Cancelamento na PPL

Este tópico explica a função de cancelamento do paralela padrões PPL (biblioteca), como cancelar o trabalho paralelo e como determinar quando um grupo de tarefas for cancelado.

Seções

  • Árvores de trabalho paralelos

  • Cancelando tarefas em paralelo

  • Cancelando algoritmos paralelos

  • Quando não usar o cancelamento

Árvores de trabalho paralelos

A PPL usa grupos de tarefas para gerenciar os cálculos e tarefas refinadas. Você pode aninhar grupos de tarefas para o formulário árvores de trabalho paralelo. A ilustração a seguir mostra uma árvore de trabalho paralelos. Nesta ilustração, tg1 e tg2 representam grupos de tarefas; t1, t2, t3, t4, and t5 represent tasks.

Uma árvore de trabalho paralelo

O exemplo a seguir mostra o código que é necessário para criar a árvore na ilustração. Neste exemplo, tg1 e tg2 são Concurrency::structured_task_group objetos; t1, t2, t3, t4, e t5 são Concurrency::task_handle objetos.

// task-tree.cpp
// compile with: /c /EHsc
#include <ppl.h>
#include <sstream>
#include <iostream>
#include <sstream>

using namespace Concurrency;
using namespace std;

void create_task_tree()
{   
   // Create a task group that serves as the root of the tree.
   structured_task_group tg1;

   // Create a task that contains a nested task group.
   auto t1 = make_task([&] {
      structured_task_group tg2;

      // Create a child task.
      auto t4 = make_task([&] {
         // TODO: Perform work here.
      });

      // Create a child task.
      auto t5 = make_task([&] {
         // TODO: Perform work here.
      });

      // Run the child tasks and wait for them to finish.
      tg2.run(t4);
      tg2.run(t5);
      tg2.wait();
   });

   // Create a child task.
   auto t2 = make_task([&] {
      // TODO: Perform work here.
   });

   // Create a child task.
   auto t3 = make_task([&] {
      // TODO: Perform work here.
   });

   // Run the child tasks and wait for them to finish.
   tg1.run(t1);
   tg1.run(t2);
   tg1.run(t3);
   tg1.wait();   
}

go to top

Cancelando tarefas em paralelo

Há duas maneiras para cancelar o trabalho paralelo. É uma maneira de chamar o Concurrency::task_group::cancel método ou a Concurrency::structured_task_group::cancel método. A outra forma é lançar uma exceção no corpo de uma função de trabalho da tarefa.

O cancel método é mais eficiente do que a manipulação de exceção no cancelamento de uma árvore de trabalho paralelos. O cancel método cancela um grupo de tarefas e quaisquer grupos de tarefas filho em uma maneira de cima para baixo. Por outro lado, a manipulação de exceção funciona de uma maneira de baixo para cima e deve cancelar a cada grupo de tarefas filho independentemente como a exceção se propaga para cima.

As seções a seguir mostram como usar o cancel método e o tratamento de exceção para cancelar o trabalho paralelo. Para obter mais exemplos Cancelar tarefas paralelas, consulte Como: Use o cancelamento para quebra de um Loop paralelo e Como: Use o tratamento de exceção para quebra de um Loop paralelo.

Usando o método para cancelar o trabalho paralelo de cancelamento

O Concurrency::task_group::cancel e Concurrency::structured_task_group::cancel métodos definir um grupo de tarefas para o estado de cancelado.

ObservaçãoObservação

O tempo de execução usa o tratamento de exceção para implementar o cancelamento. Não catch ou manipular essas exceções em seu código. Além disso, recomendamos que você escrever o código de exceção seguro nos corpos de função para suas tarefas. Por exemplo, você pode usar o É inicialização de aquisição de recursos padrão (RAII) para garantir que os recursos são manipulados corretamente quando uma exceção é lançada no corpo de uma tarefa. Para obter um exemplo completo que utiliza o padrão RAII para limpar um recurso em uma tarefa cancelável, consulte Demonstra Passo a passo: Removendo o trabalho de um segmento de Interface do usuário.

Depois de chamar cancel, o grupo de tarefas não inicia tarefas futuras. O cancel métodos podem ser chamados por várias tarefas filho. Uma tarefa cancelada faz com que o Concurrency::task_group::wait e Concurrency::structured_task_group::wait métodos para retornar Concurrency::canceled.

O cancel método afeta apenas as tarefas do filho. Por exemplo, se você cancelar o grupo de tarefas tg1 na ilustração da árvore de trabalho paralelos, todas as tarefas na árvore (t1, t2, t3, t4, e t5) são afetados. Se você cancelar o grupo de tarefas aninhados, tg2, somente tarefas t4 e t5 são afetados.

Quando você chama o cancel método, todas as tarefas de filho grupos também serão cancelados. No entanto, o cancelamento não afeta qualquer pais de grupo de tarefas em uma árvore de trabalho paralelos. Os exemplos a seguir mostram isso, baseando-se na ilustração de árvore de trabalho paralelos.

O primeiro desses exemplos cria uma função de trabalho para a tarefa t4, que é um filho do grupo de tarefas tg2. A função de trabalho chama a função work em um loop. Se qualquer chamada para work falhar, a tarefa cancela seu grupo de tarefas do pai. Isso faz com que o grupo de tarefas tg2 Inserir o estado de cancelado, mas ele não cancelar o grupo de tarefas tg1.

auto t4 = make_task([&] {
   // Perform work in a loop.
   for (int i = 0; i < 1000; ++i)
   {
      // Call a function to perform work.
      // If the work function fails, cancel the parent task
      // and break from the loop.
      bool succeeded = work(i);
      if (!succeeded)
      {
         tg2.cancel();
         break;
      }
   }         
});

Este segundo exemplo semelhante ao primeiro, exceto que a tarefa cancela o grupo de tarefas tg1. Isso afeta todas as tarefas na árvore (t1, t2, t3, t4, e t5).

auto t4 = make_task([&] {
   // Perform work in a loop.
   for (int i = 0; i < 1000; ++i)
   {
      // Call a function to perform work.
      // If the work function fails, cancel all tasks in the tree.
      bool succeeded = work(i);
      if (!succeeded)
      {
         tg1.cancel();
         break;
      }
   }   
});

O structured_task_group classe não é thread-safe. Portanto, uma tarefa filho que chama um método de seu pai structured_task_group objeto produz o comportamento não especificado. As exceções a essa regra são os structured_task_group::cancel e Concurrency::structured_task_group::is_canceling métodos. Uma tarefa filho pode chamar esses métodos para cancelar a tarefa de pai e verificar cancelamento.

Usando exceções para cancelar o trabalho paralelo

O tópico O Runtime de simultaneidade de manipulação de exceção explica como o Runtime de simultaneidade usa exceções para comunicar erros. No entanto, nem todas as exceções indicam um erro. Por exemplo, um algoritmo de pesquisa pode cancelar seu grupo de tarefas associadas ao encontrar o resultado. No entanto, como mencionado anteriormente, manipulação de exceção é menos eficiente do que usar o cancel método para cancelar o trabalho paralelo.

Quando você lançar uma exceção no corpo de uma função de trabalho que você passa para um grupo de tarefas, o tempo de execução armazena essa exceção e empacota a exceção para o contexto que aguarda o grupo de tarefas concluir. Como ocorre com o cancel método, o runtime descarta quaisquer tarefas que ainda não foi iniciado e não aceitam novas tarefas.

Neste terceiro exemplo é semelhante o um segundo, exceto que a tarefa t4 lança uma exceção para cancelar o grupo de tarefas tg2. Este exemplo usa um try–catch bloco para verificar se há cancelamento quando o grupo de tarefas tg2 aguarda suas tarefas filho para concluir. Como o primeiro exemplo, isso faz com que o grupo de tarefas tg2 Inserir o estado de cancelado, mas ele não cancelar o grupo de tarefas tg1.

structured_task_group tg2;

// Create a child task.      
auto t4 = make_task([&] {
   // Perform work in a loop.
   for (int i = 0; i < 1000; ++i)
   {
      // Call a function to perform work.
      // If the work function fails, throw an exception to 
      // cancel the parent task.
      bool succeeded = work(i);
      if (!succeeded)
      {
         throw exception("The task failed");
      }
   }         
});

// Create a child task.
auto t5 = make_task([&] {
   // TODO: Perform work here.
});

// Run the child tasks.
tg2.run(t4);
tg2.run(t5);

// Wait for the tasks to finish. The runtime marshals any exception
// that occurs to the call to wait.
try
{
   tg2.wait();
}
catch (const exception& e)
{
   wcout << e.what() << endl;
}

Esse quarto exemplo usa o tratamento de exceção para cancelar a árvore inteira de trabalho. O exemplo captura a exceção quando o grupo de tarefas tg1 aguarda suas tarefas filho concluir em vez de ao grupo de tarefas tg2 aguarda a tarefas de seu filho. Como o segundo exemplo, isso faz com que ambos os grupos de tarefas na árvore de tg1 e tg2, para inserir o estado cancelado.

// Run the child tasks.
tg1.run(t1);
tg1.run(t2);
tg1.run(t3);   

// Wait for the tasks to finish. The runtime marshals any exception
// that occurs to the call to wait.
try
{
   tg1.wait();
}
catch (const exception& e)
{
   wcout << e.what() << endl;
}

Porque o task_group::wait e structured_task_group::wait métodos lançam quando uma tarefa filho lança uma exceção, você não recebe um valor de retorno delas.

Determinando quando o trabalho for cancelado

Cancelamento é cooperativo. Portanto, não ocorre imediatamente. Se um grupo de tarefas for cancelado, chamadas de cada tarefa filho em tempo de execução podem disparar um o ponto de interrupção, que faz com que o tempo de execução lançar e capturar um tipo de exceção interna para cancelar as tarefas ativas. O Runtime de simultaneidade não define pontos de interrupção específicos; eles podem ocorrer em qualquer chamada para o tempo de execução. O runtime deve tratar as exceções que ele lança para realizar o cancelamento. Portanto, não manipular exceções desconhecidas no corpo de uma tarefa.

Se uma tarefa filho executa uma operação demorada e não chama no tempo de execução, ele deve procurar o cancelamento periodicamente e sair de maneira oportuna. O exemplo a seguir mostra uma maneira para determinar quando o trabalho for cancelado. Tarefa t4 cancela o grupo de tarefas do pai quando ele encontra um erro. Tarefa t5 ocasionalmente chama o structured_task_group::is_canceling método para verificar se há cancelamento. Se o grupo de tarefas do pai for cancelado, a tarefa t5 imprime uma mensagem e sai.

structured_task_group tg2;

// Create a child task.
auto t4 = make_task([&] {
   // Perform work in a loop.
   for (int i = 0; i < 1000; ++i)
   {
      // Call a function to perform work.
      // If the work function fails, cancel the parent task
      // and break from the loop.
      bool succeeded = work(i);
      if (!succeeded)
      {
         tg2.cancel();
         break;
      }
   }
});

// Create a child task.
auto t5 = make_task([&] {
   // Perform work in a loop.
   for (int i = 0; i < 1000; ++i)
   {
      // To reduce overhead, occasionally check for 
      // cancelation.
      if ((i%100) == 0)
      {
         if (tg2.is_canceling())
         {
            wcout << L"The task was canceled." << endl;
            break;
         }
      }

      // TODO: Perform work here.
   }
});

// Run the child tasks and wait for them to finish.
tg2.run(t4);
tg2.run(t5);
tg2.wait();

Este exemplo verifica para cancelamento em cada 100th iteração do loop de tarefa. A freqüência com que você verificar cancelamento depende da quantidade de trabalho que executa a sua tarefa e a rapidez necessárias para tarefas responder ao cancelamento.

Se você não tem acesso ao objeto de grupo de tarefas do pai, chame o Concurrency::is_current_task_group_canceling função para determinar se o grupo de tarefas do pai é cancelado.

go to top

Cancelando algoritmos paralelos

Paralelo algoritmos PPL, por exemplo, Concurrency::parallel_for, criar grupos de tarefas. Portanto, você pode usar muitas das mesmas técnicas para cancelar um algoritmo paralelo.

Os exemplos a seguir ilustram várias maneiras para cancelar um algoritmo paralelo.

O exemplo a seguir usa a Concurrency::structured_task_group::run_and_wait método para chamar o parallel_for algoritmo. O structured_task_group::run_and_wait método aguarda que a tarefa fornecida para concluir. O structured_task_group objeto permite que a função de trabalho Cancelar a tarefa.

// To enable cancelation, call parallel_for in a task group.
structured_task_group tg;

task_group_status status = tg.run_and_wait([&] {
   parallel_for(0, 100, [&](int i) {
      // Cancel the task when i is 50.
      if (i == 50)
      {
         tg.cancel();
      }
      else
      {
         // TODO: Perform work here.
      }
   });
});

// Print the task group status.
wcout << L"The task group status is: ";
switch (status)
{
case not_complete:
   wcout << L"not complete." << endl;
   break;
case completed:
   wcout << L"completed." << endl;
   break;
case canceled:
   wcout << L"canceled." << endl;
   break;
default:
   wcout << L"unknown." << endl;
   break;
}

O exemplo produz a seguinte saída.

The task group status is: canceled.

O exemplo a seguir usa o tratamento de exceção para cancelar uma parallel_for loop. O runtime empacota a exceção para o contexto de chamada.

try
{
   parallel_for(0, 100, [&](int i) {
      // Throw an exception to cancel the task when i is 50.
      if (i == 50)
      {
         throw i;
      }
      else
      {
         // TODO: Perform work here.
      }
   });
}
catch (int n)
{
   wcout << L"Caught " << n << endl;
}

O exemplo produz a seguinte saída.

Caught 50

O exemplo a seguir usa um sinalizador booleano para coordenar o cancelamento em um parallel_for loop. Cada tarefa é executada porque este exemplo não usa o cancel manipulação de exceção ou método para cancelar o conjunto geral de tarefas. Portanto, essa técnica pode ter mais sobrecarga de um mecanismo de cancelamento.

// Create a Boolean flag to coordinate cancelation.
bool canceled = false;

parallel_for(0, 100, [&](int i) {
   // For illustration, set the flag to cancel the task when i is 50.
   if (i == 50)
   {
      canceled = true;
   }

   // Perform work if the task is not canceled.
   if (!canceled)
   {
      // TODO: Perform work here.
   }
});

Cada método de cancelamento tem vantagens sobre os outros. Escolha o método adequado às suas necessidades específicas.

go to top

Quando não usar o cancelamento

O uso de cancelamento é apropriado quando cada membro de um grupo de tarefas relacionadas pode sair no momento oportuno. No entanto, existem alguns cenários onde cancelamento pode não ser apropriado para seu aplicativo. Por exemplo, porque o cancelamento da tarefa é cooperativo, o conjunto geral de tarefas não cancelará se qualquer tarefa individual estiver bloqueada. Por exemplo, se uma tarefa ainda não foi iniciada, mas ele desbloqueia a outra tarefa ativa, ele não iniciará se o grupo de tarefas é cancelado. Isso pode causar o bloqueio ocorra em seu aplicativo. Um segundo exemplo de onde o uso de cancelamento pode não ser apropriado é quando uma tarefa é cancelada, mas sua tarefa filho realiza uma operação importante, como, por exemplo, liberando um recurso. Porque o conjunto geral de tarefas é cancelado quando a tarefa pai for cancelada, a operação não será executado. Para obter um exemplo que ilustra este ponto, consulte o Understand how Cancellation and Exception Handling Affect Object Destruction seção práticas recomendadas no tópico biblioteca paralela de padrões.

go to top

Tópicos relacionados

Referência

Classe de task_group

structured_task_group classe

Função de parallel_for

Histórico de alterações

Date

History

Motivo

Março de 2011

Ao outro caso de quando não à seção de cancelamento de uso.

Aprimoramento de informações.