并发运行时使用 C++ 异常处理来传达多种错误。 这些错误包括:无效使用运行时、无法获取资源等运行时错误,以及你提供给任务和任务组的工作函数中发生的错误。 当任务或任务组引发异常时,运行时会保存该异常并将其编组到等待任务或任务组完成的上下文。 对于轻量级任务和代理等组件,运行时不会为你管理异常。 在这些情况下,你必须实现自己的异常处理机制。 本系列中描述运行时如何处理任务、任务组、轻量级任务和异步代理引发的异常,以及如何在应用程序中响应异常。
任务组和并行算法
本节介绍运行时如何处理任务组引发的异常。 本节也适用于并行算法,例如 concurrency::parallel_for,因为这些算法建立在任务组之上。
确保你了解异常对相关任务的影响。 有关如何对任务或并行算法使用异常处理的推荐实践,请参阅“并行模式库中的最佳实践”主题中的了解取消和异常处理如何影响对象销毁一节。
在传递给 concurrency::task_group 或 concurrency::structured_task_group 对象的工作函数的主体中引发异常时,运行时会存储该异常并将其封送到调用 concurrency::task_group::wait、concurrency::structured_task_group::wait、concurrency::task_group::run_and_wait 或 concurrency::structured_task_group::run_and_wait 的上下文中。 运行时还会停止任务组中的所有活动任务(包括子任务组中的任务),并丢弃任何尚未启动的任务。
以下示例显示了引发异常的工作函数的基本结构。 该示例使用一个 task_group 对象并行打印两个 point 对象的值。 print_point 工作函数将 point 对象的值打印到控制台。 如果输入值为 NULL,则工作函数会引发异常。 运行时存储此异常并将其封送到调用 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;
}
}
输出为:
X = 15, Y = 30Caught exception: point is NULL.
运行时引发的异常
调用运行时可能会导致异常。 大多数异常类型(除了 concurrency::task_canceled 和 concurrency::operation_timed_out),都表示编程错误。 这些错误通常是不可恢复的,因此不应由应用程序代码捕获或处理。 我们建议你仅在需要诊断编程错误时才在应用程序代码中捕获或处理不可恢复的错误。 但是,了解运行时定义的异常类型可以帮助你诊断编程错误。
运行时引发的异常与工作函数引发的异常的异常处理机制相同。 例如,concurrency::receive 函数如果在指定时间段内没有收到消息,则会引发 operation_timed_out。 如果 receive 在传递给任务组的工作函数中引发异常,则运行时存储该异常并将其编组到调用 task_group::wait、structured_task_group::wait、task_group::run_and_wait 或 structured_task_group::run_and_wait 的上下文中。
以下示例使用 concurrency::parallel_invoke 算法并行运行两个任务。 第一个任务等待五秒钟,然后将消息发送到消息缓冲区。 第二个任务使用 receive 函数等待三秒钟以接收来自同一消息缓冲区的消息。 如果在该时间段内没有收到消息,receive 函数将引发 operation_timed_out。
// 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;
}
}
输出为:
The operation timed out.
为防止应用程序异常终止,请确保你的代码在调用运行时时处理异常。 调用使用并发运行时的外部代码(例如第三方库)时,还要处理异常。