windows C++ 并行编程-PPL 中的取消操作(三)
并行模式库 (PPL) 中取消操作的角色、如何取消并行工作以及如何确定取消并行工作的时间。
运行时使用异常处理实现取消操作。 请勿在代码中捕捉或处理这些异常。 此外,还建议你在任务的函数体中编写异常安全的代码。 例如,可以使用获取资源即初始化 (RAII) 模式,以确保在任务体中引发异常时正确处理资源。
使用取消方法来取消并行工作
concurrency::task_group::cancel 和 concurrency::structured_task_group::cancel 方法可将任务组设置为已取消状态。 在你调用 cancel 之后,任务组不会启动将来的任务。 cancel 方法可以由多个子任务调用。 已取消状态会导致 concurrency::task_group::wait 和 concurrency::structured_task_group::wait 方法返回 concurrency::canceled。
如果任务组已取消,从每个子任务到运行时的调用可以触发一个中断点,这将导致运行时引发和捕获内部异常类型以取消活动任务。 并发运行时不定义具体的中断点;它们可以在对运行时的任何调用中出现。 运行时必须处理它引发的异常才能执行取消。 因此,不要处理任务正文中的未知异常。
如果子任务执行耗时的操作,并且不会调入运行时,它必须定期检查取消并及时退出。 下面的示例演示一种确定取消工作的时间的方法。 任务 t4 在遇到错误时取消父任务组。 任务 t5 偶尔调用 structured_task_group::is_canceling 方法来检查取消。 如果父任务组已取消,则任务 t5 打印一条消息并退出。
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();
此示例在任务循环每迭代 100 次时检查是否取消。 检查取消的频率取决于任务执行的工作量和你需要任务响应取消的速度。
如果你无权访问父任务组对象,请调用 concurrency::is_current_task_group_canceling 函数以确定是否已取消父任务组。
cancel 方法只影响子任务。 例如,如果取消并行工作树插图中的任务组 tg1,则该树中的所有任务(t1、t2、t3、t4 和 t5)都将受到影响。 如果取消嵌套的任务组 tg2,则只有任务 t4 和 t5 会受到影响。
当你调用 cancel 方法时,将同时取消所有子任务组。 但是,取消操作并不影响并行工作树中任务组的任何父级。 下面的示例通过在并行工作树插图中进行生成来演示这一点。
这些示例中的第一个示例为任务 t4(该任务是任务组 tg2 的子级)创建一个工作函数。 该工作函数在循环中调用函数 work。 如果对 work 的任何调用失败,则该任务取消其父任务组。 这将导致任务组 tg2 进入已取消状态,但不会取消任务组 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;}}
});
此第二个示例与第一个示例类似,只不过该任务将取消任务组 tg1。 这会影响树中的所有任务(t1、t2、t3、t4 和 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;}}
});
structured_task_group 类不是线程安全的。 因此,调用其父 structured_task_group 对象方法的子任务会产生未指定的行为。 此规则的例外是 structured_task_group::cancel 和 concurrency::structured_task_group::is_canceling 方法。 子任务可以调用这些方法来取消父任务组和检查取消。
注意尽管可以使用取消标记来取消由作为 task 对象的子级运行的任务组执行的工作,但不能使用 task_group::cancel 或 structured_task_group::cancel 方法来取消任务组中运行的 task 对象。