Cancelamento no PPL
Este documento explica a função de cancelamento na PPL (Biblioteca de Padrões Paralelos), como cancelar o trabalho paralelo e como determinar quando o trabalho paralelo é cancelado.
Observação
O runtime usa tratamento de exceção para implementar o cancelamento. Não trate essas exceções em seu código. Além disso, recomendamos que você escreva código com segurança de exceção nos corpos da função para suas tarefas. Por exemplo, você pode usar o padrão RAII ( Resource Acquisition Is Initialization ) para garantir que os recursos sejam tratados corretamente quando uma exceção é lançada no corpo de uma tarefa. Para obter um exemplo completo que usa o padrão RAII para limpar um recurso em uma tarefa cancelável, consulte Passo a passo: Removendo o trabalho de um thread de interface do usuário.
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.
Quando possível, use tokens de cancelamento para cancelar o trabalho. A classe concurrency::cancellation_token define um token de cancelamento.
Ao usar 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 qualquer outra tarefa solicitou o 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 deverá verificar e responder ao cancelamento.
Uma continuação baseada em valor herda o token de cancelamento de sua tarefa antecedente. Uma continuação baseada em tarefa nunca herda o token de sua tarefa antecedente.
Use o método concurrency::cancellation_token::none ao chamar um construtor ou função que usa um objeto
cancellation_token
, mas se não quiser 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 refinadas e cálculos. 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
represente grupos de tarefas; t1
, t2
, t3
, t4
e t5
representam o trabalho que os grupos de tarefas executam.
O exemplo a seguir mostra o código necessário para criar a árvore na ilustração. Neste exemplo, tg1
e tg2
são objetos de simultaneidade::structured_task_group; t1
, t2
, t3
, t4
e t5
são objetos concurrency::task_handle.
// 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 dá suporte à noção de uma árvore de trabalho. No entanto, uma árvore task
é uma árvore de dependência. Em uma árvore task
, as obras futuras são concluídas 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 da Tarefa.
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 dão suporte ao método concurrency::task_group::cancel e ao método concurrency::structured_task_group::cancel. A maneira final é lançar uma exceção no corpo de uma função de trabalho de tarefa. Não importa qual método você escolher, 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 deverá verificar e responder ao cancelamento.
Para obter mais exemplos que cancelam tarefas paralelas, consulte Passo a passo: Conectando tarefas de uso e solicitações HTTP XML, Como usar o cancelamento para interromper a partir de um loop paralelo e Como usar o tratamento de exceções para interromper a partir de um loop paralelo.
Usando um token de cancelamento para cancelar o trabalho paralelo
As classes task
, task_group
e structured_task_group
dão suporte ao cancelamento usando tokens de cancelamento. O PPL define as classes concurrency::cancellation_token_source e concurrency::cancellation_token para essa finalidade. Quando você usa um token de cancelamento para cancelar o trabalho, o runtime não inicia um novo trabalho que assina esse token. O trabalho que já está ativo pode usar a função de membro is_canceled para monitorar 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 destas maneiras:
Para objetos
task
, 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 detectar o cancelamento e retornar o mais rápido possível do corpo da tarefa quando essa função retornar
true
. (Não chamecancel_current_task
de um grupo de tarefas.)
O exemplo a seguir mostra o primeiro padrão básico para o cancelamento. O corpo da tarefa ocasionalmente verifica o 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 função cancel_current_task
é gerada; portanto, você não precisa retornar explicitamente do loop ou da função atual.
Dica
Como alternativa, você pode chamar a função concurrency::interruption_point em vez de cancel_current_task
.
É importante chamar cancel_current_task
quando você responder ao cancelamento porque ele faz a transição da tarefa para o estado cancelado. Se você retornar mais cedo em vez de chamar cancel_current_task
, a operação fará a transição para o estado concluído e todas as continuações baseadas em valor serão executadas.
Cuidado
Nunca gere task_canceled
do seu código. Chame cancel_current_task
em seu lugar.
Quando uma tarefa termina no estado cancelado, o método concurrency::task::get gera concurrency::task_canceled. (Por outro lado, concurrency::task::wait retorna task_status::canceled e não é gerada.) O exemplo a seguir ilustra esse comportamento para uma continuação baseada em tarefas. Uma continuação baseada em tarefa é sempre chamada, mesmo quando a tarefa antecedente é 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 inserem imediatamente o estado cancelado mesmo quando a tarefa anterior ainda estiver em execução. Portanto, qualquer exceção gerada pela tarefa antecedente após o cancelamento não é propagada para as tarefas de continuação. O cancelamento sempre substitui o estado da tarefa anterior. 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.
*/
Cuidado
Se você não passar o token de cancelamento para o construtor task
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á desabilitar 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 tarefa explica a diferença entre continuações baseadas em valor e baseadas em tarefas. Se você não fornecer um objeto cancellation_token
a 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 de sua tarefa antecedente.
Uma continuação baseada em tarefa nunca herda o token de cancelamento de sua 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 com falha (ou seja, uma que gera uma exceção). Nesse caso, uma continuação baseada em valor é cancelada; uma continuação baseada em tarefa não é cancelada.
Cuidado
Uma tarefa criada em outra tarefa (em outras palavras, uma tarefa aninhada) não herda o token de cancelamento da tarefa pai. Somente uma continuação baseada em valor herda o token de cancelamento de sua tarefa antecedente.
Dica
Use o método concurrency::cancellation_token::none ao chamar um construtor ou função que usa um objeto cancellation_token
, e se não quiser 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 aspecto importante disso é que os grupos de tarefas filho herdam esse token de cancelamento. Para obter um exemplo que demonstre esse conceito usando a função concurrency::run_with_cancellation_token para executar para chamar parallel_for
, consulte Cancelando algoritmos paralelos posteriormente neste documento.
Tokens de Cancelamento e Composição da Tarefa
As funções concurrency::when_all e concurrency::when_any podem ajudar a compor várias tarefas para implementar padrões comuns. Esta seção descreve como essas funções funcionam com tokens de cancelamento.
Quando você fornece um token de cancelamento para a função when_all
e when_any
função, essa função é cancelada somente quando esse token de cancelamento é cancelado ou quando uma das tarefas do participante termina em um estado cancelado ou gera uma exceção.
A função when_all
herda o token de cancelamento de cada tarefa que compõe a operação geral quando você não fornece um token de cancelamento a ele. A tarefa que é retornada de when_all
é cancelada quando qualquer um desses tokens é cancelado e pelo menos uma das tarefas do participante ainda não foi iniciada ou está em execução. Um comportamento semelhante ocorre quando uma das tarefas gera uma exceção – a tarefa que é retornada de when_all
é imediatamente cancelada com essa exceção.
O runtime escolhe o token de cancelamento para a tarefa que é retornada da função when_any
quando essa tarefa é concluída. Se nenhuma das tarefas do participante terminar em um estado concluído e uma ou mais das tarefas gerar 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 concluída no estado concluído, a tarefa que é retornada da tarefa when_any
terminará em um estado concluído. O runtime tenta escolher uma tarefa concluída cujo token não é cancelado no momento da conclusão para que a tarefa que é retornada de when_any
não seja cancelada imediatamente, embora outras tarefas em execução possam ser concluídas posteriormente.
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 cancel
, o grupo de tarefas não iniciará tarefas futuras. Os métodos cancel
podem ser chamados por várias tarefas filho. 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 filho para o runtime poderão disparar um ponto de interrupção, o que faz com que o runtime gere e capture um tipo de exceção interno para cancelar tarefas ativas. O Runtime de Simultaneidade não define pontos de interrupção específicos; eles podem ocorrer em qualquer chamada para o runtime. O runtime deve lidar com as exceções geradas para executar o cancelamento. Portanto, não manipule exceções desconhecidas no corpo de uma tarefa.
Se uma tarefa filho executar uma operação demorada e não chamar o runtime, ela deverá verificar periodicamente o cancelamento e sair em tempo hábil. 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. A tarefa t5
ocasionalmente chama o método structured_task_group::is_canceling
para verificar se há cancelamento. Se o grupo de tarefas pai for cancelado, a tarefa t5
imprimirá uma mensagem e sairá.
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 o cancelamento em cada 100a iteração do loop da tarefa. 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 do grupo de tarefas pai, chame a função concurrency::is_current_task_group_canceling para determinar se o grupo de tarefas pai foi cancelado.
O método cancel
afeta apenas tarefas filho. 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
, t4
e t5
) serão afetadas. Se você cancelar o grupo de tarefas aninhado tg2
, somente as tarefas t4
e t5
serão afetadas.
Quando você chama o método cancel
, todos os grupos de tarefas filho também são cancelados. No entanto, o cancelamento não afeta nenhum dos pais do grupo de tarefas em uma árvore de trabalho paralela. Os exemplos a seguir mostram isso com base na ilustração paralela da árvore de trabalho.
O primeiro desses exemplos cria uma função de trabalho para a tarefa t4
, que é filho do grupo de tarefas tg2
. A função de trabalho chama a função work
em um loop. Se alguma chamada para work
falhar, a tarefa cancelará seu grupo de tarefas pai. Isso faz com que o grupo de tarefas tg2
insira o estado cancelado, mas não cancela 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;
}
}
});
Esse 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;
}
}
});
A classe structured_task_group
não é segura para threads. Portanto, uma tarefa filho que chama um método de seu objeto pai structured_task_group
produz um comportamento não especificado. As exceções a essa regra são os métodos structured_task_group::cancel
e concurrency::structured_task_group::is_canceling. Uma tarefa filho pode chamar esses métodos para cancelar o grupo de tarefas pai e verificar o cancelamento.
Cuidado
Embora você possa usar um token de cancelamento para cancelar o trabalho executado por um grupo de tarefas executado como um filho de um objeto task
, você não pode usar os métodos task_group::cancel
ou structured_task_group::cancel
para cancelar objetos task
executados em um grupo de tarefas.
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ção ao cancelar uma árvore de trabalho paralela. Tokens de cancelamento e o método cancel
cancelam uma tarefa e todas as tarefas filho de cima para baixo. Por outro lado, o tratamento de exceção funciona de maneira ascendente e deve cancelar cada grupo de tarefas filho independentemente à medida que a exceção se propaga para cima. O tópico Tratamento de Exceções 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 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 cancel
para cancelar o trabalho paralelo.
Cuidado
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 de tarefas cancel
são mais eficientes e menos propensos a erros.
Quando você gera uma exceção no corpo de uma função de trabalho que você passa para um grupo de tarefas, o runtime armazena essa exceção e faz marshaling da exceção para o contexto que aguarda a conclusão do grupo de tarefas. Assim como acontece com o método cancel
, o runtime descarta todas as tarefas que ainda não foram iniciadas e não aceita novas tarefas.
Esse terceiro exemplo é semelhante ao segundo, exceto que a tarefa t4
gera uma exceção para cancelar o grupo de tarefas tg2
. Esse exemplo usa um bloco try
-catch
para verificar o cancelamento quando o grupo de tarefas tg2
aguarda a conclusão de suas tarefas filho. Como no primeiro exemplo, isso faz com que o grupo de tarefas tg2
insira o estado cancelado, mas não cancela 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 toda a árvore de trabalho. O exemplo captura a exceção quando o grupo de tarefas tg1
aguarda a conclusão de suas tarefas filho em vez de quando o grupo de tarefas tg2
aguarda suas tarefas filho. Como o segundo exemplo, isso faz com que os dois grupos de tarefas na árvore, tg1
e tg2
, insiram 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;
}
Como os métodos task_group::wait
e structured_task_group::wait
são lançados quando uma tarefa filho gera uma exceção, você não recebe um valor retornado deles.
Cancelando algoritmos paralelos
Algoritmos paralelos no PPL, por exemplo, parallel_for
, baseiam-se 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 seguinte exemplo usa a função run_with_cancellation_token
para chamar o algoritmo parallel_for
. A função run_with_cancellation_token
usa um token de cancelamento como um argumento e chama a função de trabalho fornecida de forma síncrona. Como algoritmos paralelos são criados em tarefas, eles 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 método structured_task_group::run_and_wait
aguarda a conclusão da tarefa fornecida. O objeto structured_task_group
permite que a função de trabalho cancele 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;
}
Este exemplo gerencia a seguinte saída.
The task group status is: canceled.
O exemplo a seguir usa o tratamento de exceção para cancelar um loop parallel_for
. O runtime realiza marshaling da 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;
}
Este exemplo gerencia a seguinte saída.
Caught 50
O exemplo a seguir usa um sinalizador booliano para coordenar o cancelamento em um loop parallel_for
. Cada tarefa é executada porque esse exemplo não usa o método cancel
ou a manipulação 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 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 suas vantagens exclusivas: Escolha o método que atende às suas necessidades específicas.
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, há alguns cenários em que o cancelamento pode não ser apropriado para seu aplicativo. 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, ela não será iniciada se o grupo de tarefas for cancelado. Isso pode fazer com que o deadlock ocorra em seu aplicativo. Um segundo exemplo de onde o uso do cancelamento pode não ser apropriado é quando uma tarefa é cancelada, mas sua tarefa filho executa uma operação importante, como liberar um recurso. Como o conjunto geral de tarefas é cancelado quando a tarefa pai é cancelada, essa operação não será executada. Para obter um exemplo que ilustra esse ponto, consulte a seção Entender como o cancelamento e o tratamento de exceções afetam a destruição de objetos nas práticas recomendadas no tópico da Biblioteca de Padrões Paralelos.
Tópicos Relacionados
Título | Descrição |
---|---|
Como usar cancelamento para interromper um loop paralelo | Mostra como usar o cancelamento para implementar um algoritmo de pesquisa paralelo. |
Como usar tratamento de exceções para interromper um loop paralelo | Mostra como usar a classe task_group para escrever um algoritmo de pesquisa para uma estrutura de árvore básica. |
Tratamento de exceção | Descreve como o runtime lida com exceções geradas 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 não estruturadas em seus aplicativos. |
Algoritmos paralelos | Descreve os algoritmos paralelos, que executam simultaneamente o 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 task (Runtime de Simultaneidade)