基于任务(thread)取消引发的这一系列问题,让我们看看WinRT提供的task可以带来哪些改善。先看个例子:
cancellation_token_source cts;(注:wait(1000)是为了保证在被取消前task能被正常创建,否则在调用cancel时该task还未正常初始化执行。如果在Metro程序中,以上代码由于存在1s的延时,不能直接运行在UI线程中,否则会阻塞UI线程导致异常,请包在create_task中来执行)
auto token = cts.get_token();
auto t = create_task([]
{
bool isCanceled = false;
while (isCanceled == false)
{
if (is_task_cancellation_requested())
{
cancel_current_task();
}
}
}, token);
wait(1000);
cts.cancel();
// Wait for the task to cancel.
t.wait();
初看处理逻辑其实跟传统很多线程库的封装有点类似。简单解释一下,创建一个cancellation_token_source类型的用于取消任务的标记,获得该标记的token传递给create_task创建的task,当调用cancellation_token_source的cancel方法后,任务可以通过is_task_cancellation_requested和cancel_current_task来判断和终止一个task。把代码继续完善一下:
cancellation_token_source cts;
auto token = cts.get_token();
auto t = create_task([]
{
bool isCanceled = false;
while (isCanceled == false)
{
if (is_task_cancellation_requested())
{
cancel_current_task();
}
}
}, token).then([](task<void> t){
try {
t.get();
} catch (task_canceled& tt) {
// handle the task cancel
}
});
wait(1000);
cts.cancel();
// Wait for the task to cancel.
t.wait();
由于在task中调用cancel_current_task,会抛出task_canceled异常,在then中task-based类型任务的延续处理中,通过task的get方法可以捕获该异常。关于task-based与value-based任务的区别,以及task异常处理请参考如下文章: 【Windows8开发】异步编程进阶篇之 create_async, create_task, make_task区别与联系
【Windows8开发】异步编程进阶篇之 Platform::Exception及task间的异常控制
从上面的示例可以看出,结合task-based和value-based以及task的get方法,可以很方便的控制一个序列任务的执行,比如A,B两个task,A执行完希望执行B,如果希望A任务被取消时B任务也不执行,则可以把B的处理设置为value-based,相反则使用task-based,如果希望在task被取消时获得通知,则可以如上通过get方法来捕获cancel异常来进行处理,而WinRT还提供另一种通过concurrency::cancellation_token::register_callback注册回调的方式来响应task的取消操作:
cancellation_token_source cts;
auto token = cts.get_token();
cancellation_token_registration cookie;
cookie = token.register_callback([token, &cookie]()
{
token.deregister_callback(cookie);
// do something in callback
});
auto t = create_task([]
{
while(true) {
if (is_task_cancellation_requested())
cancel_current_task();
}
}, token);
wait(1000);
cts.cancel();
t.wait();
通过register_callback把回调方法注册给token,然后把该token传递给task,当task被取消时会触发此回调。 以上示例中都是在起始task中调用is_task_cancellation_requested来判断task是否被取消,那在后续的一系列then的延长task中呢?看如下两段代码: 第一段:
cancellation_token_source cts;
auto token = cts.get_token();
auto t = create_task([]
{
// do something here
}, token).then([](){
while(true) {
if (is_task_cancellation_requested())
cancel_current_task();
}
});
wait(1000);
cts.cancel();
t.wait();
第二段:
cancellation_token_source cts;
auto token = cts.get_token();
auto t = create_task([]
{
// do something here
}, token).then([](task<void> t){
while(true) {
if (is_task_cancellation_requested())
cancel_current_task();
}
});
wait(1000);
cts.cancel();
t.wait();
执行后会发现,第一段能正常工作,而第二段task不会终止,cancel的token在第二段中无效,为什么呢? 这里涉及一个概念,当后续task为value-based时,它会继续沿用起始任务中的token,当为task-based时,则不会,只能显式的把token传递给task-based型延续任务,处理代码如下:
cancellation_token_source cts;
auto token = cts.get_token();
auto t = create_task([]
{
// do something here
}, token).then([token](task<void> t){
while(true) {
if (token.is_canceled())
cancel_current_task();
}
});
wait(1000);
cts.cancel();
t.wait();
顺便再说明一个概念,当使用create_async创建task时,task的取消方法略有不同,create_async返回IAsyncAction等类型,可以直接调用其Cancel方法来终止task,而不需要显示的去创建cancellation_token_source。
IAsyncAction^ op = create_async([](cancellation_token token)
{
return create_task([token]{
while(1) {
if (token.is_canceled())
cancel_current_task();
}
});
});
op->Cancel();
但是需要注意的是当在create_async中又通过create_task创建了内部task后,如果希望在调用Cancel函数时能触发内部task也终止,则需要显式的传递cancellation_token给内部task,代码如下:
IAsyncAction^ op = create_async([](cancellation_token token)
{
return create_task([token]{
while(1) {
if (token.is_canceled())
cancel_current_task();
}
});
});
op->Cancel();
最后,再简单看下task group中如何取消任务,处理很简单,只show代码,不解释了。
structured_task_group tg;task group相关细节请参考 《 【Windows8开发】异步编程进阶篇之 task group的几种方式及其间的区别 》。
auto t1 = make_task([&] {
tg.cancel();
});
auto t2 = make_task([&] {
while(true) {
if (tg.is_canceling()) {
break;
}
}
});
tg.run(t1);
tg.run(t2);
tg.wait();
总结一下,如果你理解了task异常控制以及task cancellation常用的API,你肯定能体会到通过WinRT的task来终止线程时的安全与便捷,只要把以上提到的一些关键概念搞清楚,相信开头所述的那些问题应该都能控制和避免。