[Windows翻译]如何让你的C++/WinRT异步操作更快地响应取消,第1部分。

183 阅读2分钟

原文地址: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时,了解更多关于这些后门是如何工作的。


www.deepl.com 翻译