Partilhar via


Cancelamento no PPL

Este documento explica a função do cancelamento na Biblioteca de Padrões Paralelos (PPL), como cancelar o trabalho paralelo e como determinar quando o trabalho paralelo é cancelado.

Observação

O tempo de execução usa o tratamento de exceções para implementar o cancelamento. Não capture ou manipule essas exceções em seu código. Além disso, recomendamos que você escreva um código seguro para exceções nos corpos de função para suas tarefas. Por exemplo, você pode usar o padrão RAII (Resource Acquisition Is Initialization ) para garantir que os recursos sejam manipulados corretamente quando uma exceção é lançada no corpo de uma tarefa. Para obter um exemplo completo que usa o padrão RAII para libertar um recurso numa tarefa que pode ser cancelada, consulte Tutorial: Removendo trabalho de um thread de User-Interface.

Pontos Principais

  • O cancelamento é cooperativo e envolve a coordenação entre o código que solicita o cancelamento e a tarefa que responde ao cancelamento.

  • Sempre que possível, use tokens de cancelamento para cancelar o trabalho. A classe concurrency::cancellation_token define um token de cancelamento.

  • Quando você usa tokens de cancelamento, use o método concurrency::cancellation_token_source::cancel para iniciar o cancelamento e a função concurrency::cancel_current_task para responder ao cancelamento. Use o método concurrency::cancellation_token::is_canceled para verificar se alguma outra tarefa solicitou cancelamento.

  • O cancelamento não ocorre imediatamente. Embora o novo trabalho não seja iniciado se uma tarefa ou grupo de tarefas for cancelado, o trabalho ativo deve verificar e responder ao cancelamento.

  • Uma continuação baseada em valor herda o token de cancelamento da tarefa precedente. Uma continuação baseada em tarefa nunca herda o token de sua tarefa antecedente.

  • Use o método concurrency::cancellation_token::none quando chamar um construtor ou função que usa um cancellation_token objeto, mas não deseja que a operação seja cancelável. Além disso, se você não passar um token de cancelamento para o construtor concurrency::task ou a função concurrency::create_task , essa tarefa não poderá ser cancelada.

Neste documento

Árvores de trabalho paralelas

O PPL usa tarefas e grupos de tarefas para gerenciar tarefas e cálculos refinados. Você pode aninhar grupos de tarefas para formar árvores de trabalho paralelo. A ilustração a seguir mostra uma árvore de trabalho paralela. Nesta ilustração, tg1 e tg2 representar grupos de tarefas; t1, t2, t3, t4, e t5 representar o trabalho que os grupos de tarefas executam.

Uma árvore de trabalho paralela.

O exemplo a seguir mostra o código necessário para criar a árvore na ilustração. Neste exemplo, tg1 e tg2 são concurrency::structured_task_group objetos; t1, t2, t3, , t4e 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();   
}

Você também pode usar a classe concurrency::task_group para criar uma árvore de trabalho semelhante. A classe concurrency::task também suporta a noção de uma árvore de trabalho. No entanto, uma task árvore é uma árvore de dependência. Em uma task árvore, os trabalhos futuros são concluídos após o trabalho atual. Em uma árvore de grupo de tarefas, o trabalho interno é concluído antes do trabalho externo. Para obter mais informações sobre as diferenças entre tarefas e grupos de tarefas, consulte Paralelismo de tarefas.

[Topo]

Cancelando tarefas paralelas

Há várias maneiras de cancelar o trabalho paralelo. A maneira preferida é usar um token de cancelamento. Os grupos de tarefas também suportam o método concurrency::task_group::cancel e o método concurrency::structured_task_group::cancel. A maneira final é lançar uma exceção no corpo de uma função de tarefa. Não importa qual método você escolha, entenda que o cancelamento não ocorre imediatamente. Embora o novo trabalho não seja iniciado se uma tarefa ou grupo de tarefas for cancelado, o trabalho ativo deve verificar e responder ao cancelamento.

Para obter mais exemplos que cancelam tarefas paralelas, consulte Passo a passo: Conectando-se usando tarefas e solicitações HTTP XML, Como usar o cancelamento para interromper um loop paralelo e Como usar o tratamento de exceções para interromper um loop paralelo.

Usando um token de cancelamento para cancelar o trabalho paralelo

As taskclasses , task_group, e structured_task_group suportam o cancelamento através do uso de tokens de cancelamento. O PPL define as classes concurrency::cancellation_token_source e concurrency::cancellation_token para este fim. Quando você usa um token de cancelamento para cancelar o trabalho, o tempo de execução não inicia um novo trabalho que se inscreve nesse token. O trabalho que já está ativo pode usar a função-membro is_canceled para monitorizar o token de cancelamento e parar quando puder.

Para iniciar o cancelamento, chame o método concurrency::cancellation_token_source::cancel. Você responde ao cancelamento das seguintes maneiras:

  • Para task objetos, use a função concurrency::cancel_current_task . cancel_current_task cancela a tarefa atual e qualquer uma de suas continuações baseadas em valor. (Ele não cancela o token de cancelamento associado à tarefa ou suas continuações.)

  • Para grupos de tarefas e algoritmos paralelos, use a função concurrency::is_current_task_group_canceling para detetar o cancelamento e retornar o mais rápido possível do corpo da tarefa quando essa função retornar true. (Não chame cancel_current_task de um grupo de tarefas.)

O exemplo a seguir mostra o primeiro padrão básico para cancelamento de tarefas. O corpo da tarefa verifica ocasionalmente se há cancelamento dentro de um loop.

// task-basic-cancellation.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <concrt.h>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

bool do_work()
{
    // Simulate work.
    wcout << L"Performing work..." << endl;
    wait(250);
    return true;
}

int wmain()
{
    cancellation_token_source cts;
    auto token = cts.get_token();

    wcout << L"Creating task..." << endl;

    // Create a task that performs work until it is canceled.
    auto t = create_task([&]
    {
        bool moreToDo = true;
        while (moreToDo)
        {
            // Check for cancellation.
            if (token.is_canceled())
            {
                // TODO: Perform any necessary cleanup here...

                // Cancel the current task.
                cancel_current_task();
            }
            else 
            {
                // Perform work.
                moreToDo = do_work();
            }
        }
    }, token);

    // Wait for one second and then cancel the task.
    wait(1000);

    wcout << L"Canceling task..." << endl;
    cts.cancel();

    // Wait for the task to cancel.
    wcout << L"Waiting for task to complete..." << endl;
    t.wait();

    wcout << L"Done." << endl;
}

/* Sample output:
    Creating task...
    Performing work...
    Performing work...
    Performing work...
    Performing work...
    Canceling task...
    Waiting for task to complete...
    Done.
*/

A cancel_current_task função é lançada, portanto, você não precisa retornar explicitamente do loop ou função atual.

Sugestão

Como alternativa, você pode chamar a função concurrency::interruption_point em vez de cancel_current_task.

É importante chamar cancel_current_task ao responder ao cancelamento, pois isto transfere a tarefa para o estado cancelado. Se você retornar cedo em vez de chamar cancel_current_task, a operação transitará para o estado concluído e todas as continuações baseadas em valor serão executadas.

Atenção

Nunca lance task_canceled a partir do seu código. Telefone cancel_current_task em vez disso.

Quando uma tarefa termina no estado cancelado, o método concurrency::task::get lança concurrency::task_canceled. (Por outro lado, concurrency::task::wait retorna task_status::canceled e não lança.) O exemplo a seguir ilustra esse comportamento para uma continuação baseada em tarefas. Uma continuação orientada por tarefa é sempre chamada, mesmo que a tarefa antecedente seja cancelada.

// task-canceled.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    auto t1 = create_task([]() -> int
    {
        // Cancel the task.
        cancel_current_task();
    });

    // Create a continuation that retrieves the value from the previous.
    auto t2 = t1.then([](task<int> t)
    {
        try
        {
            int n = t.get();
            wcout << L"The previous task returned " << n << L'.' << endl;
        }
        catch (const task_canceled& e)
        {
            wcout << L"The previous task was canceled." << endl;
        }
    });

    // Wait for all tasks to complete.
    t2.wait();
}
/* Output:
    The previous task was canceled.
*/

Como as continuações baseadas em valor herdam o token de sua tarefa antecedente, a menos que tenham sido criadas com um token explícito, as continuações entram imediatamente no estado cancelado, mesmo quando a tarefa antecedente ainda está em execução. Portanto, qualquer exceção lançada pela tarefa antecedente após o cancelamento não é propagada para as tarefas de continuação. O cancelamento sempre prevalece sobre o estado da tarefa antecedente. O exemplo a seguir é semelhante ao anterior, mas ilustra o comportamento de uma continuação baseada em valor.

auto t1 = create_task([]() -> int
{
    // Cancel the task.
    cancel_current_task();
});

// Create a continuation that retrieves the value from the previous.
auto t2 = t1.then([](int n)
{
    wcout << L"The previous task returned " << n << L'.' << endl;
});

try
{
    // Wait for all tasks to complete.
    t2.get();
}
catch (const task_canceled& e)
{
    wcout << L"The task was canceled." << endl;
}
/* Output:
    The task was canceled.
*/

Atenção

Se você não passar um token de cancelamento para o task construtor ou a função concurrency::create_task , essa tarefa não será cancelável. Além disso, você deve passar o mesmo token de cancelamento para o construtor de quaisquer tarefas aninhadas (ou seja, tarefas que são criadas no corpo de outra tarefa) para cancelar todas as tarefas simultaneamente.

Talvez você queira executar um código arbitrário quando um token de cancelamento for cancelado. Por exemplo, se o usuário escolher um botão Cancelar na interface do usuário para cancelar a operação, você poderá desativar esse botão até que o usuário inicie outra operação. O exemplo a seguir mostra como usar o método concurrency::cancellation_token::register_callback para registrar uma função de retorno de chamada que é executada quando um token de cancelamento é cancelado.

// task-cancellation-callback.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    cancellation_token_source cts;
    auto token = cts.get_token();

    // An event that is set in the cancellation callback.
    event e;

    cancellation_token_registration cookie;
    cookie = token.register_callback([&e, token, &cookie]()
    {
        wcout << L"In cancellation callback..." << endl;
        e.set();

        // Although not required, demonstrate how to unregister 
        // the callback.
        token.deregister_callback(cookie);
    });

    wcout << L"Creating task..." << endl;

    // Create a task that waits to be canceled.
    auto t = create_task([&e]
    {
        e.wait();
    }, token);

    // Cancel the task.
    wcout << L"Canceling task..." << endl;
    cts.cancel();

    // Wait for the task to cancel.
    t.wait();

    wcout << L"Done." << endl;
}
/* Sample output:
    Creating task...
    Canceling task...
    In cancellation callback...
    Done.
*/

O documento Paralelismo de tarefas explica a diferença entre continuações baseadas em valor e em tarefas. Se você não fornecer um cancellation_token objeto para uma tarefa de continuação, a continuação herdará o token de cancelamento da tarefa antecedente das seguintes maneiras:

  • Uma continuação baseada em valor sempre herda o token de cancelamento da tarefa antecedente.

  • Uma continuação baseada em tarefa nunca herda o token de cancelamento da tarefa antecedente. A única maneira de tornar uma continuação baseada em tarefa cancelável é passar explicitamente um token de cancelamento.

Esses comportamentos não são afetados por uma tarefa defeituosa (ou seja, que lança uma exceção). Nesse caso, uma continuação baseada em valor é cancelada; Uma continuação baseada em tarefas não é cancelada.

Atenção

Uma tarefa que é criada dentro de outra tarefa (ou seja, uma tarefa aninhada) não herda o token de cancelamento da tarefa-mãe. Apenas uma continuação baseada em valor herda o token de cancelamento da sua tarefa antecedente.

Sugestão

Use o método concurrency::cancellation_token::none quando você chamar um construtor ou função que usa um cancellation_token objeto e você não deseja que a operação seja cancelável.

Você também pode fornecer um token de cancelamento para o construtor de um objeto task_group ou structured_task_group. Um aspeto importante disso é que os grupos de tarefas filho herdam esse token de cancelamento. Para obter um exemplo que demonstra esse conceito usando a função concurrency::run_with_cancellation_token a ser executada para chamar parallel_for, consulte Cancelando algoritmos paralelos posteriormente neste documento.

[Topo]

Tokens de cancelamento e gestão de tarefas

As funções concurrency::when_all e concurrency::when_any podem ajudá-lo a compor várias tarefas para implementar padrões comuns. Esta seção descreve como essas funções funcionam com tokens de cancelamento.

Quando se fornece um token de cancelamento para quer a função when_all, quer a função when_any, essa função é cancelada apenas quando esse token de cancelamento é cancelado ou quando uma das tarefas envolvidas termina em estado cancelado ou lança uma exceção.

A when_all função herda o token de cancelamento de cada tarefa que compõe a operação geral quando você não fornece um token de cancelamento para ela. A tarefa que é retornada de when_all é cancelada quando qualquer um desses tokens é cancelado e pelo menos uma das tarefas participantes ainda não foi iniciada ou está em execução. Um comportamento semelhante ocorre quando uma das tarefas lança uma exceção - a tarefa que é retornada de when_all é imediatamente cancelada com essa exceção.

O tempo de execução escolhe o token de cancelamento para a tarefa que é retornada quando essa tarefa é concluída pela função when_any. Se nenhuma das tarefas do participante terminar em um estado concluído e uma ou mais das tarefas lançarem uma exceção, uma das tarefas lançadas será escolhida para concluir o when_any e seu token será escolhido como o token para a tarefa final. Se mais de uma tarefa for finalizada no estado concluído, a tarefa retornada de when_any termina também em estado concluído. O sistema em execução tenta escolher uma tarefa concluída cujo token não seja cancelado no momento da conclusão, para garantir que a tarefa retornada de when_any não seja imediatamente cancelada, mesmo que outras tarefas em execução possam ser concluídas posteriormente.

[Topo]

Usando o método cancel para cancelar o trabalho paralelo

Os métodos concurrency::task_group::cancel e concurrency::structured_task_group::cancel definem um grupo de tarefas para o estado cancelado. Depois de chamar o cancel, o grupo de tarefas não inicia futuras tarefas. Os cancel métodos podem ser chamados por várias subtarefas. Uma tarefa cancelada faz com que os métodos concurrency::task_group::wait e concurrency::structured_task_group::wait retornem concurrency::canceled.

Se um grupo de tarefas for cancelado, as chamadas de cada tarefa filha para o tempo de execução podem acionar um ponto de interrupção, levando o tempo de execução a lançar e capturar um tipo de exceção interna para cancelar tarefas ativas. O Concurrency Runtime não define pontos de interrupção específicos; eles podem ocorrer em qualquer chamada para o tempo de execução. O runtime deve lidar com as exceções que lança para executar o cancelamento. Portanto, não manipule exceções desconhecidas na execução de uma tarefa.

Se uma tarefa filha executar uma operação demorada e não chamar o tempo de execução, deverá verificar periodicamente se há cancelamento e, em caso afirmativo, encerrar rapidamente. O exemplo a seguir mostra uma maneira de determinar quando o trabalho é cancelado. A tarefa t4 cancela o grupo de tarefas pai quando encontra um erro. Tarefa t5 chama ocasionalmente o método structured_task_group::is_canceling para verificar se foi cancelado. Se o grupo de tarefas pai for cancelado, a tarefa t5 imprimirá uma mensagem e será encerrada.

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 
      // cancellation.
      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 o cancelamento em cada 100ª iteração do loop de tarefas. A frequência com que você verifica o cancelamento depende da quantidade de trabalho que sua tarefa executa e da rapidez com que você precisa para que as tarefas respondam ao cancelamento.

Se você não tiver acesso ao objeto de grupo de tarefas pai, chame a função concurrency::is_current_task_group_canceling para determinar se o grupo de tarefas pai será cancelado.

O cancel método afeta apenas tarefas filhas. Por exemplo, se você cancelar o grupo tg1 de tarefas na ilustração da árvore de trabalho paralela, todas as tarefas na árvore (t1, t2, t3, t4e t5) serão afetadas. Se cancelar o grupo de tarefas aninhado, tg2, somente as tarefas t4 e t5 serão afetadas.

Quando você chama o cancel método, todos os grupos de tarefas filho também são cancelados. No entanto, o cancelamento não afeta quaisquer pais do grupo de tarefas numa árvore de trabalho paralela. Os exemplos a seguir mostram isso com base na ilustração da árvore de trabalho paralela.

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

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 tg1de tarefas. Isso afeta todas as tarefas na árvore (t1, t2, t3, t4e 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;
      }
   }   
});

A structured_task_group classe não é thread-safe. Portanto, uma tarefa filho que chama um método do seu objeto pai structured_task_group produz um comportamento não especificado. As exceções a esta regra são os métodos structured_task_group::cancel e concorrência::structured_task_group::is_canceling. Uma tarefa filha pode chamar esses métodos para cancelar o grupo de tarefas do pai e verificar se há cancelamento.

Atenção

Embora você possa usar um token de cancelamento para cancelar o trabalho executado por um grupo de tarefas executado como filho de um task objeto, não é possível usar os task_group::cancel métodos ou structured_task_group::cancel para cancelar task objetos executados em um grupo de tarefas.

[Topo]

Usando exceções para cancelar o trabalho paralelo

O uso de tokens de cancelamento e o método cancel são mais eficientes do que o tratamento de exceções no cancelamento de uma árvore de trabalho paralela. Os tokens de cancelamento e o método cancel cancelam uma tarefa e quaisquer tarefas secundárias de forma hierárquica. Por outro lado, o tratamento de exceções funciona de baixo para cima e deve cancelar cada grupo de tarefas filho de forma independente à medida que a exceção se propaga para cima na hierarquia. O tópico Tratamento de exceções explica como o Concurrency Runtime usa exceções para comunicar erros. No entanto, nem todas as exceções indicam um erro. Por exemplo, um algoritmo de pesquisa pode cancelar sua tarefa associada quando encontrar o resultado. No entanto, como mencionado anteriormente, o tratamento de exceções é menos eficiente do que usar o método para cancelar o cancel trabalho paralelo.

Atenção

Recomendamos que você use exceções para cancelar o trabalho paralelo somente quando necessário. Os tokens de cancelamento e os métodos do grupo cancel de tarefas são mais eficientes e menos propensos a erros.

Quando se lança uma exceção no corpo de uma função de trabalho que é passada para um grupo de tarefas, o tempo de execução armazena essa exceção e organiza a exceção para o contexto que aguarda a conclusão do grupo de tarefas. Assim como o cancel método, o tempo de execução descarta todas as tarefas que ainda não foram iniciadas e não aceita novas tarefas.

Este terceiro exemplo é semelhante ao segundo, exceto que a tarefa t4 lança uma exceção para cancelar o grupo tg2de tarefas. Este exemplo usa um bloco try-catch para verificar se há cancelamento quando o grupo de tarefas tg2 aguarda a conclusão das suas tarefas filhas. Como o primeiro exemplo, isso faz com que o grupo tg2 de tarefas entre no estado cancelado, mas não cancela o grupo tg1de tarefas.

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

Este quarto exemplo usa o tratamento de exceção para cancelar toda a árvore de trabalho. O exemplo captura a exceção quando o grupo tg1 de tarefas aguarda a conclusão de suas tarefas filhas, em vez de quando o grupo tg2 de tarefas aguarda suas tarefas filhas. Como o segundo exemplo, isso faz com que ambos os grupos de tarefas na árvore tg1 e tg2, entrem no 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;
}

Como os task_group::wait métodos e structured_task_group::wait são lançados quando uma tarefa filho lança uma exceção, você não recebe um valor de retorno deles.

[Topo]

Cancelando algoritmos paralelos

Algoritmos paralelos no PPL, por exemplo, parallel_forse baseiam em 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 de cancelar um algoritmo paralelo.

O exemplo a seguir usa a run_with_cancellation_token função para chamar o parallel_for algoritmo. A run_with_cancellation_token função usa um token de cancelamento como argumento e chama a função de trabalho fornecida de forma síncrona. Como os algoritmos paralelos são baseados em tarefas, herdam o token de cancelamento da tarefa pai. Portanto, parallel_for pode responder ao cancelamento.

// cancel-parallel-for.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

int wmain()
{
    // Call parallel_for in the context of a cancellation token.
    cancellation_token_source cts;
    run_with_cancellation_token([&cts]() 
    {
        // Print values to the console in parallel.
        parallel_for(0, 20, [&cts](int n)
        {
            // For demonstration, cancel the overall operation 
            // when n equals 11.
            if (n == 11)
            {
                cts.cancel();
            }
            // Otherwise, print the value.
            else
            {
                wstringstream ss;
                ss << n << endl;
                wcout << ss.str();
            }
        });
    }, cts.get_token());
}
/* Sample output:
    15
    16
    17
    10
    0
    18
    5
*/

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

// To enable cancellation, 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;
}

Este exemplo produz o seguinte resultado.

The task group status is: canceled.

O exemplo a seguir usa o tratamento de exceções para cancelar um parallel_for loop. O tempo de execução controla a exceção ao 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;
}

Este exemplo produz o seguinte resultado.

Caught 50

O exemplo a seguir usa uma variável booleana para coordenar o cancelamento num parallel_for loop. Todas as tarefas são executadas porque este exemplo não usa o método ou o cancel tratamento de exceções para cancelar o conjunto geral de tarefas. Portanto, essa técnica pode ter mais sobrecarga computacional do que um mecanismo de cancelamento.

// Create a Boolean flag to coordinate cancellation.
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 que se adapta às suas necessidades específicas.

[Topo]

Quando não usar o cancelamento

O uso do cancelamento é apropriado quando cada membro de um grupo de tarefas relacionadas pode sair em tempo hábil. No entanto, existem alguns cenários em que o cancelamento pode não ser adequado para a sua candidatura. Por exemplo, como o cancelamento de tarefas é cooperativo, o conjunto geral de tarefas não será cancelado se qualquer tarefa individual for bloqueada. Por exemplo, se uma tarefa ainda não tiver sido iniciada, mas desbloquear outra tarefa ativa, esta última não começará se o grupo de tarefas for cancelado. Isso pode fazer com que ocorra um impasse em seu aplicativo. Um segundo exemplo de onde o uso de cancelamento pode não ser apropriado ocorre quando uma tarefa é cancelada, mas a sua tarefa subordinada executa uma operação importante, como liberar um recurso. Porque o conjunto geral de tarefas é cancelado quando a tarefa principal é cancelada, essa operação não será executada. Para obter um exemplo que ilustra esse ponto, consulte a seção Compreender como o cancelamento e o tratamento de exceções afetam a destruição de objetos no tópico Práticas recomendadas na Biblioteca de padrões paralelos.

[Topo]

Título Descrição
Como: Usar o cancelamento para interromper um loop paralelo Mostra como usar o cancelamento para implementar um algoritmo de pesquisa paralela.
Como usar o tratamento de exceção para interromper um ciclo paralelo Mostra como usar a task_group classe para escrever um algoritmo de pesquisa para uma estrutura de árvore básica.
Tratamento de exceções Descreve como o tempo de execução lida com exceções lançadas por grupos de tarefas, tarefas leves e agentes assíncronos e como responder a exceções em seus aplicativos.
Paralelismo de tarefas Descreve como as tarefas se relacionam com grupos de tarefas e como você pode usar tarefas não estruturadas e estruturadas em seus aplicativos.
Algoritmos paralelos Descreve os algoritmos paralelos, que executam simultaneamente trabalho em coleções de dados
Biblioteca de Padrões Paralelos (PPL) Fornece uma visão geral da Biblioteca de Padrões Paralelos.

Referência

Classe de tarefa (Concurrency Runtime)

cancellation_token_source Classe

cancellation_token Classe

task_group Classe

structured_task_group Classe

Função parallel_for