原文地址:devblogs.microsoft.com/oldnewthing…
原文作者:devblogs.microsoft.com/oldnewthing…
发布时间:2020年7月22日
C++/WinRT为Windows Runtime异步动作和操作提供了一个实现,它们甚至支持取消,即使你的代码没有意识到这一点。
每当¹你的coroutine执行co_await时,C++/WinRT库都会检查该coroutine是否已经被取消了²。如果是这样,那么它就会放弃该coroutine并进入Canceled状态。
IAsyncAction ProcessAllWidgetsAsync()
{
auto widgets = co_await GetAllWidgetsAsync();
for (auto&& widget : widgets) {
ProcessWidget(widget);
}
co_await ReportStatusAsync(WidgetsProcessed);
}
这个功能是将所有的小组件收集起来,然后逐一处理。但是,如果有成千上万的widgets,而你又想取消操作,那么你可以用这个函数来处理这些widgets。
IAsyncAction DoOperationAsync()
{
// Remember the operation so we can cancel it.
operation = ProcessAllWidgetsAsync();
co_await operation;
}
void CancelOperation()
{
operation.Cancel();
}
当Cancel被调用的时候,C++/WinRT库就会记住coroutine已经被取消,并寻找机会停止coroutine。但是现在,coroutine正忙于在ProcessAllWidgets里面运行循环,C++/WinRT库直到co_await的时候才会得到控制,报告状态。一旦发生这种情况,coroutine就会停止执行,并报告其取消状态³。
这可能是几个小时以后的事情。
你可以通过轮询取消来加快你的coroutine的取消过程。
IAsyncAction ProcessAllWidgetsAsync()
{
auto cancellation = co_await get_cancellation_token();
auto widgets = co_await GetAllWidgetsAsync();
for (auto&& widget : widgets) {
if (cancellation()) co_return;
ProcessWidget(widget);
}
co_await ReportStatusAsync(WidgetsProcessed);
}
co_await get_cancellation_token()会产生一个当前coroutine的取消令牌⁴。
在处理每个widget之前,我们会检查是否已经被取消。如果是,那么我们就立即放弃。co_return是C++/WinRT库重新获得控制权的另一个点,那也是处理待取消的。
但是等等,如果调用者试图在GetAllWidgetsAsync进行时取消操作怎么办?现在控制权在另一个异步操作里面,可能需要很长的时间才能得到所有的widgets。下一次,我们将研究如何将取消传播到依赖的coroutine中。
¹这条规则有一些例外,但它足够真实。
²我用两个L拼写cancelled。
³这突出了使用RAII类型进行所有清理的重要性。如果coroutine由于取消而停止执行,那么它的自动对象就会根据C++的通常规则被销毁,这就是你的异常清理发生的地方。关于这个问题,我们很快就会讲到。
⁴co_await get_cancellation_token()是co_await总是检查取消的规则的例外之一。在这种情况下,co_await get_cancellation_token()实际上并没有 "等待 "什么。相反,它是进入C++/WinRT库的一个后门。我们将在未来某个未指明的时间点,研究如何在C++20中实现你自己的coroutines时,了解更多关于这些后门是如何工作的。