问题 如何将task.Wait(CancellationToken)转换为await语句?


所以, task.Wait() 可以转化为 await task。当然,语义是不同的,但这大致是我将如何转换阻塞代码 Waits 用异步代码 awaits

我的问题是如何改造 task.Wait(CancellationToken) 各自的 await 声明?


11675
2017-09-02 21:35


起源

可能重复 等待方法中的取消令牌 - John Koerner
不,我看过那个,但没有找到答案。请删除复制标记,除非说明它们为何重复。 - mark


答案:


创建一个新的 Task 表示现有任务但具有额外的取消令牌非常简单。你只需要打电话 ContinueWith 在任务上,使用新标记,并在延续体中传播结果/异常。

public static Task WithCancellation(this Task task,
    CancellationToken token)
{
    return task.ContinueWith(t => t.GetAwaiter().GetResult(), token);
}
public static Task<T> WithCancellation<T>(this Task<T> task,
    CancellationToken token)
{
    return task.ContinueWith(t => t.GetAwaiter().GetResult(), token);
}

这允许你写 task.WithCancellation(cancellationToken) 将令牌添加到任务,然后您可以 await


7
2017-09-03 20:11



为什么不用 t.Result?所以两者的实现都是一样的 Task<T> 和 Task? - i3arnon
@ I3arnon Result 没有正确的错误传播语义。但对称性很好,是的。 - Servy
@Servy - 很棒的答案。我从没想过将取消令牌传递给延续实际上取消了任务本身。 - mark
@mark它没有。它取消了你正在等待的延续。 - i3arnon
@ I3arnon - 这是我一开始就想到的。但这不是正在发生的事情。我有一个永远不完整的任务,直到明确取消,错误或设置相应的任务 TaskCompletionSource 实例。然而,当我触发取消时,它确实取消了任务。我有一个演示此行为的程序,请参阅编辑3 stackoverflow.com/questions/25632533/... (你对这个问题非常熟悉:-)) - mark


await 用于异步方法/委托,它们接受一个 CancellationToken 所以当你打电话时你应该通过一个(即 await Task.Delay(1000, cancellationToken)),或者他们没有,他们不能真正取消(例如等待I / O结果)。

但是,您可以使用此扩展方法放弃*这些类型的任务:

public static Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
    return task.IsCompleted // fast-path optimization
        ? task
        : task.ContinueWith(
            completedTask => completedTask.GetAwaiter().GetResult(),
            cancellationToken,
            TaskContinuationOptions.ExecuteSynchronously,
            TaskScheduler.Default);
}

用法:

await task.WithCancellation(cancellationToken);

*被放弃的任务不会被取消,但您的代码表现得像是一样。它要么以结果/异常结束,要么永远保持活力。


8
2017-09-02 21:40



哇,这是意外的参与。 - mark
@mark那是因为 async-await 需要不同的心态。 “常规”委托任务和“承诺”异步任务虽然共享一种类型,但却是完全不同的概念...... 两种任务 - i3arnon
+1为优雅的解决方案。我在自己的代码库中有一个几乎相同的扩展方法,除了我只打扰注册取消回调if cancellationToken.CanBeCanceled 回报 true。 - Mike Strobel
我有一些奇怪的情况,没有调用取消回调 - 请参阅编辑。 - mark
@mark你有一些奇怪而复杂的代码。你有一个竞争条件 InternalTaskScheduler。尝试使用相同的示例 Test(false) 在两个电话中,你会对结果感到惊讶。也试试移动 Thread.Sleep(1000) 就在之后 ts.RunInline(t);。我的猜测是,如果它足够快,线程吞下了 TaskCanceledException 在空 catch (OperationCanceledException) - i3arnon