并行模式库 (PPL) 中取消操作的角色、如何取消并行工作以及如何确定取消并行工作的时间。
运行时使用异常处理实现取消操作。 请勿在代码中捕捉或处理这些异常。 此外,还建议你在任务的函数体中编写异常安全的代码。 例如,可以使用获取资源即初始化 (RAII) 模式,以确保在任务体中引发异常时正确处理资源。
使用取消标记来取消并行工作
task、task_group 和 structured_task_group 类支持通过使用取消标记进行取消。 PPL 为此目标定义 concurrency::cancellation_token_source 和 concurrency::cancellation_token 类。 当使用取消标记来取消工作时,运行时不会启动订阅此标记的新工作。 已处于活动状态的工作可以使用 is_canceled 成员函数监视取消标记,并在可能时停止。
若要初始化取消,请调用 concurrency::cancellation_token_source::cancel 方法。 可以采用以下方法响应取消:
对于 task 对象,请使用 concurrency::cancel_current_task 函数。 cancel_current_task 取消当前任务及其任何基于值的延续。 它不会取消与任务或其延续关联的取消标记。
对于任务组和并行算法,使用 concurrency::is_current_task_group_canceling 函数取消,并在此函数返回 true 时尽快从任务正文返回。 请勿从任务组调用 cancel_current_task。
下面的示例演示用于进行任务取消的第一个基本模式。 任务正文偶尔检查循环内的取消。
// 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.
*/
cancel_current_task 函数引发;因此,你不必从当前循环或函数显示返回。
或者,可以调用 concurrency::interruption_point 函数而不是 cancel_current_task。
响应取消时,请务必调用 cancel_current_task,因为任务转换为已取消状态。 如果提前返回而不是调用 cancel_current_task,则操作转换为已完成状态并且所有基于值的延续都会运行。
注意,切勿从代码中引发 task_canceled。 请改为调用 cancel_current_task。
当任务以已取消状态结束时,concurrency::task::get 方法引发 concurrency::task_canceled。 相反,concurrency::task::wait 返回 task_status::canceled 并且不会引发。下面的示例阐释了基于任务的延续的这种行为。 始终调用基于任务的延续,即使前面的任务已取消。
// 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.
*/
由于基于值延续的继承其前面的任务的标记,因此除非它们是使用显式标记创建的,即使前面的任务仍正在执行,延续也会立即进入已取消状态。 因此,在取消后由前面的任务引发的任何异常都不会传播到延续任务。 取消始终替代前面的任务的状态。 下面的示例与前面的示例类似,但说明了基于值的延续的行为。
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.
*/
如果,没有将此取消标记传递给 task 构造函数或 concurrency::create_task 函数,则该任务是不可取消的。 此外,还必须将相同的取消标记传递给任何嵌套的任务,即在另一项任务的正文中创建的任务的构造函数,以同时取消所有任务。
你可能想要在取消标记被取消时运行任意代码。 例如,如果用户在用户界面上选择“取消”按钮来取消操作,则可禁用该按钮,直到用户启动其他操作。 下面的示例演示了如何使用 concurrency::cancellation_token::register_callback 方法来注册在取消标记被取消时运行的回调函数。
// 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.
*/
任务并行文档介绍了基于值的延续和基于任务的延续之间的差异。 如果未向延续任务提供 cancellation_token 对象,则延续通过以下方式从前面的任务继承取消标记:
基于值的延续始终继承前面的任务的取消标记。
基于任务的延续从不继承前面的任务的取消标记。 使基于任务的延续可取消的唯一方法是显式传递一个取消标记。
这些行为不会受出错任务(即引发异常的任务)影响。 在这种情况下,基于值的延续被取消;基于任务的延续没有被取消。
注意在另一个任务中创建的任务(即嵌套任务)不会继承父任务的取消标记。 只有基于值的继承延续前面的任务的取消标记。
当你调用一个采用 cancellation_token 对象的构造函数或函数,且希望操作不可取消时,请使用 concurrency::cancellation_token::none 方法。
还可以向 task_group 或 structured_task_group 对象的构造函数提供取消标记。 一个重要方面是子任务组继承此取消标记。