原文地址:devblogs.microsoft.com/oldnewthing…
原文作者:devblogs.microsoft.com/oldnewthing…
发布时间:2020年7月23日
我们上次看到,你可以通过轮询取消来加速C++/WinRT的coroutine的取消。但这只有在需要响应取消的是顶层的coroutine时才有效。但通常情况下,你的coroutine会调用其他coroutine,如果这些其他coroutine中的一个需要很长时间,你的主coroutine就没有机会响应取消,直到它重新获得控制权。
我们可以让主 coroutine 更快地响应取消吗?
当然可以。
你可以传递一个自定义的委托给C++/WinRT取消标记的回调方法,以便在coroutine被取消时立即得到通知。
IAsyncAction ProcessAllWidgetsAsync()
{
auto cancellation = co_await get_cancellation_token();
cancellation.callback([] { /* zomg! cancelled! */ });
auto widgets = co_await GetAllWidgetsAsync();
for (auto&& widget : widgets) {
if (cancellation()) co_return;
ProcessWidget(widget);
}
co_await ReportStatusAsync(WidgetsProcessed);
}
当coroutine被取消时,会立即调用取消回调。这是你加速你的coroutine死亡的机会。例如,我们可以通过取消GetAllWidgetsAsync调用来实现。
IAsyncAction ProcessAllWidgetsAsync()
{
auto cancellation = co_await get_cancellation_token();
auto operation = GetAllWidgetsAsync();
cancellation.callback([operation] { operation.Cancel(); });
auto widgets = co_await operation;
for (auto&& widget : widgets) {
if (cancellation()) co_return;
ProcessWidget(widget);
}
co_await ReportStatusAsync(WidgetsProcessed);
}
如果ProcessAllWidgetsAsync被取消,我们就会把这个取消传播给GetAllWidgetsAsync操作,希望它放弃获取所有widgets的尝试,把控制权还给ProcessAllWidgetsAsync。co_await会以hresult_canceled失败,然后会从coroutine中传播出去,导致整个coroutine被取消。
这是个很常见的模式,你可以为它写一个包装器。
template<typename Async, typename Token>
std::decay_t<Async> MakeCancellable(Async&& async, Token&& token)
{
token.callback([async] { async.Cancel(); });
return std::forward<Async>(async);
}
现在我们只需将异步操作封装在一个MakeCancellable里面。
IAsyncAction ProcessAllWidgetsAsync()
{
auto cancellation = co_await get_cancellation_token();
auto widgets = co_await MakeCancellable(GetAllWidgetsAsync(), cancellation);
for (auto&& widget : widgets) {
if (cancellation()) co_return;
ProcessWidget(widget);
}
co_await MakeCancellable(ReportStatusAsync(WidgetsProcessed), cancellation);
}
练习。如果在GetAllWidgets完成后,ProcessAllWidgetsAsync被取消会怎样?