Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
O Concurrency Runtime usa o tratamento de exceções C++ para comunicar muitos tipos de erros. Esses erros incluem uso inválido do tempo de execução, erros de tempo de execução, como falha na aquisição de um recurso, e erros que ocorrem em funções de trabalho que você fornece a tarefas e grupos de tarefas. Quando uma tarefa ou grupo de tarefas lança uma exceção, o tempo de execução mantém essa exceção e a direciona para o contexto que aguarda a conclusão da tarefa ou do grupo de tarefas. Para componentes como tarefas leves e agentes, o ambiente de execução não gere exceções automaticamente. Nesses casos, você deve implementar seu próprio mecanismo de tratamento de exceções. Este tópico descreve como o tempo de execução lida com exceções lançadas por tarefas, grupos de tarefas, tarefas leves e agentes assíncronos e como responder a exceções em seus aplicativos.
Pontos Principais
Quando uma tarefa ou grupo de tarefas lança uma exceção, o tempo de execução mantém essa exceção e a direciona para o contexto que aguarda a conclusão da tarefa ou do grupo de tarefas.
Sempre que possível, envolva cada chamada para concorrência::task::get e concorrência::task::wait com um bloco
try/catchpara lidar com erros dos quais seja possível recuperar-se. O tempo de execução encerra o aplicativo se uma tarefa lançar uma exceção e essa exceção não for detetada pela tarefa, por uma de suas continuações ou pelo aplicativo principal.Uma continuação baseada em tarefas é sempre executada; não importa se a tarefa anterior foi concluída com êxito, lançou uma exceção ou foi cancelada. Uma continuação baseada em valor não será executada se a tarefa antecedente for lançada ou cancelada.
Como as continuações baseadas em tarefas são sempre executadas, considere se deseja adicionar uma continuação baseada em tarefas no final da sua cadeia de continuação. Isso pode ajudar a garantir que seu código observe todas as exceções.
O tempo de execução lança concorrência::task_canceled quando chamas concorrência::task::get e essa tarefa é cancelada.
O tempo de execução não gerencia exceções para tarefas leves e agentes.
Neste documento
Tarefas e Continuações
Esta secção descreve como o tempo de execução lida com exceções que são lançadas por objetos concurrency::task e suas continuações. Para obter mais informações sobre o modelo de tarefa e continuação, consulte Paralelismo de tarefas.
Quando se gera uma exceção no corpo de uma função de trabalho que se passa para um objeto task, o tempo de execução armazena essa exceção e a encaminha para o contexto que chama concurrency::task::get ou concurrency::task::wait. O documento Paralelismo de tarefas descreve continuações baseadas em tarefas versus baseadas em valor, mas, para resumir, uma continuação baseada em valor usa um parâmetro de tipo T e uma continuação baseada em tarefa usa um parâmetro de tipo task<T>. Se uma tarefa lançada tiver uma ou mais continuações baseadas em valor, essas continuações não serão agendadas para serem executadas. O exemplo a seguir ilustra esse comportamento:
// eh-task.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
wcout << L"Running a task..." << endl;
// Create a task that throws.
auto t = create_task([]
{
throw exception();
});
// Create a continuation that prints its input value.
auto continuation = t.then([]
{
// We do not expect this task to run because
// the antecedent task threw.
wcout << L"In continuation task..." << endl;
});
// Wait for the continuation to finish and handle any
// error that occurs.
try
{
wcout << L"Waiting for tasks to finish..." << endl;
continuation.wait();
// Alternatively, call get() to produce the same result.
//continuation.get();
}
catch (const exception& e)
{
wcout << L"Caught exception." << endl;
}
}
/* Output:
Running a task...
Waiting for tasks to finish...
Caught exception.
*/
Uma continuação baseada em tarefas permite que o utilizador manipule qualquer exceção lançada pela tarefa antecedente. Uma continuação baseada em tarefas continua a ser executada; não importa se a tarefa foi concluída com êxito, gerou uma exceção ou foi cancelada. Quando uma tarefa lança uma exceção, suas continuações baseadas em tarefas são agendadas para serem executadas. O exemplo a seguir mostra uma tarefa que sempre é lançada. A tarefa tem duas continuações; um é baseado em valores e o outro é baseado em tarefas. A exceção baseada em tarefa sempre é executada e, portanto, pode capturar a exceção lançada pela tarefa antecedente. Quando o exemplo aguarda a conclusão de ambas as continuações, a exceção é lançada novamente porque a exceção de tarefa é sempre lançada quando task::get ou task::wait é chamada.
// eh-continuations.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
wcout << L"Running a task..." << endl;
// Create a task that throws.
auto t = create_task([]() -> int
{
throw exception();
return 42;
});
//
// Attach two continuations to the task. The first continuation is
// value-based; the second is task-based.
// Value-based continuation.
auto c1 = t.then([](int n)
{
// We don't expect to get here because the antecedent
// task always throws.
wcout << L"Received " << n << L'.' << endl;
});
// Task-based continuation.
auto c2 = t.then([](task<int> previousTask)
{
// We do expect to get here because task-based continuations
// are scheduled even when the antecedent task throws.
try
{
wcout << L"Received " << previousTask.get() << L'.' << endl;
}
catch (const exception& e)
{
wcout << L"Caught exception from previous task." << endl;
}
});
// Wait for the continuations to finish.
try
{
wcout << L"Waiting for tasks to finish..." << endl;
(c1 && c2).wait();
}
catch (const exception& e)
{
wcout << L"Caught exception while waiting for all tasks to finish." << endl;
}
}
/* Output:
Running a task...
Waiting for tasks to finish...
Caught exception from previous task.
Caught exception while waiting for all tasks to finish.
*/
Recomendamos que você use continuações baseadas em tarefas para capturar exceções que você é capaz de lidar. Como as continuações baseadas em tarefas são sempre executadas, considere se deseja adicionar uma continuação baseada em tarefas no final da sua cadeia de continuação. Isso pode ajudar a garantir que seu código observe todas as exceções. O exemplo a seguir mostra uma cadeia de continuação básica baseada em valor. A terceira tarefa na cadeia é lançada e, portanto, quaisquer continuações baseadas em valor que a seguem não são executadas. No entanto, a continuação final é baseada em tarefas e, portanto, sempre é executada. Esta continuação final lida com a exceção lançada pela terceira tarefa.
Recomendamos que você pegue as exceções mais específicas que puder. Você pode omitir essa continuação final baseada em tarefas se não tiver exceções específicas para capturar. Qualquer exceção permanecerá sem tratamento e poderá encerrar o aplicativo.
// eh-task-chain.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
int n = 1;
create_task([n]
{
wcout << L"In first task. n = ";
wcout << n << endl;
return n * 2;
}).then([](int n)
{
wcout << L"In second task. n = ";
wcout << n << endl;
return n * 2;
}).then([](int n)
{
wcout << L"In third task. n = ";
wcout << n << endl;
// This task throws.
throw exception();
// Not reached.
return n * 2;
}).then([](int n)
{
// This continuation is not run because the previous task throws.
wcout << L"In fourth task. n = ";
wcout << n << endl;
return n * 2;
}).then([](task<int> previousTask)
{
// This continuation is run because it is task-based.
try
{
// The call to task::get rethrows the exception.
wcout << L"In final task. result = ";
wcout << previousTask.get() << endl;
}
catch (const exception&)
{
wcout << L"<exception>" << endl;
}
}).wait();
}
/* Output:
In first task. n = 1
In second task. n = 2
In third task. n = 4
In final task. result = <exception>
*/
Sugestão
Você pode usar o método concurrency::task_completion_event::set_exception para associar uma exceção a um evento de conclusão de tarefa. O documento Task Parallelism descreve a classe concurrency::task_completion_event com mais detalhes.
concurrency::task_canceled é um tipo de exceção de tempo de execução importante relacionado a task. O tempo de execução lança task_canceled quando se chama task::get e essa tarefa é cancelada. (Por outro lado, task::wait retorna task_status::cancelado e não lança.) Pode-se interceptar e gestionar esta exceção a partir de uma continuação baseada em tarefas ou ao chamar task::get. Para obter mais informações sobre o cancelamento de tarefas, consulte Cancelamento no PPL.
Atenção
Nunca lance task_canceled a partir do seu código. Chame concorrência::cancel_current_task em vez disso.
O tempo de execução encerra o aplicativo se uma tarefa lançar uma exceção e essa exceção não for detetada pela tarefa, por uma de suas continuações ou pelo aplicativo principal. Se a sua aplicação falhar, pode configurar o Visual Studio para interromper a execução quando são lançadas exceções C++. Depois de diagnosticar o local da exceção não tratada, use uma continuação baseada em tarefas para manipulá-la.
A seção Exceções lançadas pelo tempo de execução neste documento descreve como trabalhar com exceções de tempo de execução com mais detalhes.
[Topo]
Grupos de tarefas e algoritmos paralelos
Esta seção descreve como o tempo de execução lida com exceções lançadas por grupos de tarefas. Esta seção também se aplica a algoritmos paralelos, como concurrency::p arallel_for, porque esses algoritmos se baseiam em grupos de tarefas.
Atenção
Certifique-se de que compreende os efeitos que as exceções têm nas tarefas dependentes. Para obter práticas recomendadas sobre como usar o tratamento de exceções com tarefas ou algoritmos paralelos, 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.
Para obter mais informações sobre grupos de tarefas, consulte Paralelismo de tarefas. Para obter mais informações sobre algoritmos paralelos, consulte Algoritmos paralelos.
Quando se lança uma exceção no corpo de uma função de trabalho que é passada para um objeto concurrency::task_group ou concurrency::structured_task_group, o tempo de execução armazena essa exceção e redireciona-a para o contexto que chama concurrency::task_group::wait, concurrency::structured_task_group::wait, concurrency::task_group::run_and_wait ou concurrency::structured_task_group::run_and_wait. O ambiente de execução também interrompe todas as tarefas ativas do grupo de tarefas (incluindo aquelas em grupos de tarefas filho) e descarta qualquer tarefa que ainda não tenha sido iniciada.
O exemplo a seguir mostra a estrutura básica de uma função de trabalho que lança uma exceção. O exemplo usa um task_group objeto para imprimir os valores de dois point objetos em paralelo. A print_point função de trabalho imprime os valores de um point objeto no console. A função work lança uma exceção se o valor de entrada for NULL. O tempo de execução armazena essa exceção e a direciona para o contexto que chama task_group::wait.
// eh-task-group.cpp
// compile with: /EHsc
#include <ppl.h>
#include <iostream>
#include <sstream>
using namespace concurrency;
using namespace std;
// Defines a basic point with X and Y coordinates.
struct point
{
int X;
int Y;
};
// Prints the provided point object to the console.
void print_point(point* pt)
{
// Throw an exception if the value is NULL.
if (pt == NULL)
{
throw exception("point is NULL.");
}
// Otherwise, print the values of the point.
wstringstream ss;
ss << L"X = " << pt->X << L", Y = " << pt->Y << endl;
wcout << ss.str();
}
int wmain()
{
// Create a few point objects.
point pt = {15, 30};
point* pt1 = &pt;
point* pt2 = NULL;
// Use a task group to print the values of the points.
task_group tasks;
tasks.run([&] {
print_point(pt1);
});
tasks.run([&] {
print_point(pt2);
});
// Wait for the tasks to finish. If any task throws an exception,
// the runtime marshals it to the call to wait.
try
{
tasks.wait();
}
catch (const exception& e)
{
wcerr << L"Caught exception: " << e.what() << endl;
}
}
Este exemplo produz o seguinte resultado.
X = 15, Y = 30Caught exception: point is NULL.
Para obter um exemplo completo que usa o tratamento de exceções em um grupo de tarefas, consulte Como usar o tratamento de exceções para interromper um loop paralelo.
[Topo]
Exceções lançadas pelo runtime
Uma exceção pode resultar de uma chamada para o ambiente de execução. A maioria dos tipos de exceção, exceto concorrência::task_canceled e concorrência::operation_timed_out, indicam um erro de programação. Esses erros geralmente são irrecuperáveis e, portanto, não devem ser detetados ou manipulados pelo código do aplicativo. Sugerimos que você só detete ou manipule erros irrecuperáveis no código do aplicativo quando precisar diagnosticar erros de programação. No entanto, compreender os tipos de exceção definidos pelo tempo de execução pode ajudá-lo a diagnosticar erros de programação.
O mecanismo de tratamento de exceções é o mesmo para exceções lançadas pelo tempo de execução como exceções lançadas por funções de trabalho. Por exemplo, a função concurrency::receive lança uma exceção operation_timed_out quando não recebe uma mensagem no período de tempo especificado. Se receive lançar uma exceção em uma função de trabalho que você passa para um grupo de tarefas, o tempo de execução armazena essa exceção e a direciona para o contexto que chama task_group::wait, structured_task_group::wait, task_group::run_and_wait, ou structured_task_group::run_and_wait.
O exemplo a seguir usa o algoritmo concurrency::parallel_invoke para executar duas tarefas em paralelo. A primeira tarefa aguarda cinco segundos e, em seguida, envia uma mensagem para um buffer de mensagens. A segunda tarefa usa a receive função para aguardar três segundos para receber uma mensagem do mesmo buffer de mensagens. A função receive lança operation_timed_out se não receber a mensagem no período de tempo.
// eh-time-out.cpp
// compile with: /EHsc
#include <agents.h>
#include <ppl.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
single_assignment<int> buffer;
int result;
try
{
// Run two tasks in parallel.
parallel_invoke(
// This task waits 5 seconds and then sends a message to
// the message buffer.
[&] {
wait(5000);
send(buffer, 42);
},
// This task waits 3 seconds to receive a message.
// The receive function throws operation_timed_out if it does
// not receive a message in the specified time period.
[&] {
result = receive(buffer, 3000);
}
);
// Print the result.
wcout << L"The result is " << result << endl;
}
catch (operation_timed_out&)
{
wcout << L"The operation timed out." << endl;
}
}
Este exemplo produz o seguinte resultado.
The operation timed out.
Para evitar o encerramento anormal da sua aplicação, certifique-se de que o seu código lida com exceções quando fizer chamadas para o runtime. Também manipule exceções quando você chamar um código externo que usa o Concurrency Runtime, por exemplo, uma biblioteca de terceiros.
[Topo]
Exceções múltiplas
Se uma tarefa ou algoritmo paralelo receber várias exceções, o tempo de execução gerenciará apenas uma dessas exceções no contexto de chamada. O tempo de execução não garante qual exceção ele processa.
O exemplo a seguir usa o parallel_for algoritmo para imprimir números no console. Ele lança uma exceção se o valor de entrada for menor que algum valor mínimo ou maior que algum valor máximo. Neste exemplo, várias funções de trabalho podem gerar uma exceção.
// eh-multiple.cpp
// compile with: /EHsc
#include <ppl.h>
#include <iostream>
#include <sstream>
using namespace concurrency;
using namespace std;
int wmain()
{
const int min = 0;
const int max = 10;
// Print values in a parallel_for loop. Use a try-catch block to
// handle any exceptions that occur in the loop.
try
{
parallel_for(-5, 20, [min,max](int i)
{
// Throw an exception if the input value is less than the
// minimum or greater than the maximum.
// Otherwise, print the value to the console.
if (i < min)
{
stringstream ss;
ss << i << ": the value is less than the minimum.";
throw exception(ss.str().c_str());
}
else if (i > max)
{
stringstream ss;
ss << i << ": the value is greater than than the maximum.";
throw exception(ss.str().c_str());
}
else
{
wstringstream ss;
ss << i << endl;
wcout << ss.str();
}
});
}
catch (exception& e)
{
// Print the error to the console.
wcerr << L"Caught exception: " << e.what() << endl;
}
}
A seguir, é apresentada uma saída de exemplo para este caso.
8293104567Caught exception: -5: the value is less than the minimum.
[Topo]
Cancelamento
Nem todas as exceções indicam um erro. Por exemplo, um algoritmo de pesquisa pode usar o tratamento de exceções para interromper sua tarefa associada quando encontrar o resultado. Para obter mais informações sobre como usar mecanismos de cancelamento em seu código, consulte Cancelamento no PPL.
[Topo]
Tarefas leves
Uma tarefa leve é uma tarefa que você agenda diretamente de um objeto concurrency::Scheduler . Tarefas leves têm menos sobrecarga do que tarefas comuns. No entanto, o tempo de execução não captura exceções que são lançadas por tarefas leves. Em vez disso, a exceção é capturada pelo manipulador de exceção não tratado, que, por padrão, encerra o processo. Portanto, use um mecanismo apropriado de tratamento de erros em seu aplicativo. Para obter mais informações sobre tarefas leves, consulte Agendador de tarefas.
[Topo]
Agentes assíncronos
Tal como nas tarefas leves, o tempo de execução não gere exceções lançadas por agentes assíncronos.
O exemplo a seguir mostra uma maneira de lidar com exceções em uma classe que deriva de concurrency::agent. Este exemplo define a points_agent classe. O points_agent::run método lê point objetos do buffer de mensagens e os imprime no console. O run método lança uma exceção se receber um NULL ponteiro.
O método run envolve todo o trabalho em um bloco try-catch. O catch bloco armazena a exceção em um buffer de mensagens. A aplicação verifica se o agente encontrou um erro lendo a partir deste buffer após o agente terminar.
// eh-agents.cpp
// compile with: /EHsc
#include <agents.h>
#include <iostream>
using namespace concurrency;
using namespace std;
// Defines a point with x and y coordinates.
struct point
{
int X;
int Y;
};
// Informs the agent to end processing.
point sentinel = {0,0};
// An agent that prints point objects to the console.
class point_agent : public agent
{
public:
explicit point_agent(unbounded_buffer<point*>& points)
: _points(points)
{
}
// Retrieves any exception that occurred in the agent.
bool get_error(exception& e)
{
return try_receive(_error, e);
}
protected:
// Performs the work of the agent.
void run()
{
// Perform processing in a try block.
try
{
// Read from the buffer until we reach the sentinel value.
while (true)
{
// Read a value from the message buffer.
point* r = receive(_points);
// In this example, it is an error to receive a
// NULL point pointer. In this case, throw an exception.
if (r == NULL)
{
throw exception("point must not be NULL");
}
// Break from the loop if we receive the
// sentinel value.
else if (r == &sentinel)
{
break;
}
// Otherwise, do something with the point.
else
{
// Print the point to the console.
wcout << L"X: " << r->X << L" Y: " << r->Y << endl;
}
}
}
// Store the error in the message buffer.
catch (exception& e)
{
send(_error, e);
}
// Set the agent status to done.
done();
}
private:
// A message buffer that receives point objects.
unbounded_buffer<point*>& _points;
// A message buffer that stores error information.
single_assignment<exception> _error;
};
int wmain()
{
// Create a message buffer so that we can communicate with
// the agent.
unbounded_buffer<point*> buffer;
// Create and start a point_agent object.
point_agent a(buffer);
a.start();
// Send several points to the agent.
point r1 = {10, 20};
point r2 = {20, 30};
point r3 = {30, 40};
send(buffer, &r1);
send(buffer, &r2);
// To illustrate exception handling, send the NULL pointer to the agent.
send(buffer, reinterpret_cast<point*>(NULL));
send(buffer, &r3);
send(buffer, &sentinel);
// Wait for the agent to finish.
agent::wait(&a);
// Check whether the agent encountered an error.
exception e;
if (a.get_error(e))
{
cout << "error occurred in agent: " << e.what() << endl;
}
// Print out agent status.
wcout << L"the status of the agent is: ";
switch (a.status())
{
case agent_created:
wcout << L"created";
break;
case agent_runnable:
wcout << L"runnable";
break;
case agent_started:
wcout << L"started";
break;
case agent_done:
wcout << L"done";
break;
case agent_canceled:
wcout << L"canceled";
break;
default:
wcout << L"unknown";
break;
}
wcout << endl;
}
Este exemplo produz o seguinte resultado.
X: 10 Y: 20
X: 20 Y: 30
error occurred in agent: point must not be NULL
the status of the agent is: done
Como o try-catch bloco existe fora do while loop, o agente termina o processamento quando encontra o primeiro erro. Se o try-catch bloco estivesse dentro do while loop, o agente continuaria após a ocorrência de um erro.
Este exemplo armazena exceções em um buffer de mensagens para que outro componente possa monitorar o agente em busca de erros enquanto ele é executado. Este exemplo usa um objeto concurrency::single_assignment para armazenar o erro. No caso em que um agente lida com várias exceções, a classe armazena single_assignment apenas a primeira mensagem que é passada para ele. Para armazenar apenas a última exceção, use a classe concurrency::overwrite_buffer . Para armazenar todas as exceções, use a classe concurrency::unbounded_buffer . Para obter mais informações sobre esses blocos de mensagens, consulte Blocos de mensagens assíncronas.
Para obter mais informações sobre agentes assíncronos, consulte Agentes assíncronos.
[Topo]
Resumo
[Topo]
Ver também
Runtime de Concorrência
Paralelismo de tarefas
Algoritmos paralelos
Cancelamento no PPL
Agendador de Tarefas
Agentes assíncronos