与等待继续的ContinueWith(delegate,CancellationToken)的等效

我有这种情况:

private Task LongRunningTask = /* Something */; private void DoSomethingMore(Task previousTask) { } public Task IndependentlyCancelableSuccessorTask(CancellationToken cancellationToken) { return LongRunningTask.ContinueWith(DoSomethingMore, cancellationToken); } 

特别是,我在这里感兴趣的行为在MSDN的关于Continuation Tasks的页面中详细说明如下:

在这些情况下,延续进入“ Canceled状态:

  • […]
  • 当继续传递时, System.Threading.CancellationToken作为参数传递,并且在继续运行之前,令牌的IsCancellationRequested属性为true 。 在这种情况下,延续不会启动并转换为Canceled状态。

上面的代码有效。 但是,我正在将尽可能多的继续转换为使用await关键字。

是否存在使用await的等效项,允许在等待任务完成之前取消继续?

以下应该这样做,虽然看起来有点尴尬:

 private Task LongRunningTask = /* Something */; private void DoSomethingMore() { } public async Task IndependentlyCancelableSuccessorTask( CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var tcs = new TaskCompletionSource(); using (cancellationToken.Register(() => tcs.TrySetCanceled())) await Task.WhenAny(LongRunningTask, tcs.Task); cancellationToken.ThrowIfCancellationRequested(); DoSomethingMore(); } 

[更新]根据svick的建议,在这里它被塑造成一个帮手,基于Stephen Toub的实施然后与等待模式:

 public static class TaskExt { ///  /// Use: await LongRunningTask.Then(DoSomethingMore, cancellationToken) ///  public static async Task Then( this Task antecedent, Action continuation, CancellationToken token) { await antecedent.When(token); continuation(); } ///  /// Use: await LongRunningTask.When(cancellationToken) ///  public static async Task When( this Task antecedent, CancellationToken token) { token.ThrowIfCancellationRequested(); var tcs = new TaskCompletionSource(); using (token.Register(() => tcs.TrySetCanceled())) await Task.WhenAny(antecedent, tcs.Task); token.ThrowIfCancellationRequested(); } struct Empty { }; } 

也许,第一个ThrowIfCancellationRequested()是多余的,但我没有彻底考虑所有边缘情况。

虽然这个答案在概念上与Noseratio相同,但我对实现的一些细节并不满意,因此我发布了我提议的帮助程序实现,以便其他人就此问题对其进行评论。

 public static async Task WhenNotCanceled(this Task mainTask, CancellationToken cancellationToken) { if (!cancellationToken.CanBeCanceled) { return await mainTask.ConfigureAwait(false); } cancellationToken.ThrowIfCancellationRequested(); Task completedTask; var cancellationTaskSource = new TaskCompletionSource(); using (cancellationToken.Register(() => cancellationTaskSource.TrySetCanceled(), useSynchronizationContext: false) completedTask = await Task.WhenAny(mainTask, cancellationTaskSource.Task).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); return await completedTask.ConfigureAwait(false); } public static async Task WhenNotCanceled(this Task mainTask, CancellationToken cancellationToken) { if (!cancellationToken.CanBeCanceled) { await mainTask.ConfigureAwait(false); return; } cancellationToken.ThrowIfCancellationRequested(); Task completedTask; var cancellationTaskSource = new TaskCompletionSource(); using (cancellationToken.Register(() => cancellationTaskSource.TrySetCanceled(), useSynchronizationContext: false) completedTask = await Task.WhenAny(mainTask, cancellationTaskSource.Task).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); await completedTask.ConfigureAwait(false); } 

没有取消的异步模式:

 public async Task IndependentlyCancelableSuccessorTask() { await LongRunningTask; DoSomethingMore(); } 

取消和WhenNotCanceled异步模式:

 public async Task IndependentlyCancelableSuccessorTask(CancellationToken cancellationToken) { await LongRunningTask.WhenNotCanceled(cancellationToken); DoSomethingMore(); } 

我的答案与@Jean Hominal的答案略有不同,并且还采用了@Noseratio的方法:

 public static class TaskExtensionMethods { public static Task OrWhenCancelled(this Task mainTask, CancellationToken cancellationToken) { if (!cancellationToken.CanBeCanceled) return mainTask; return OrWhenCancelled_(mainTask, cancellationToken); } private static async Task OrWhenCancelled_(this Task mainTask, CancellationToken cancellationToken) { Task cancellationTask = Task.Delay(Timeout.Infinite, cancellationToken); await Task.WhenAny(mainTask, cancellationTask).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); return await mainTask; } public static Task OrWhenCancelled(this Task mainTask, CancellationToken cancellationToken) { if (!cancellationToken.CanBeCanceled) return mainTask; return OrWhenCancelled_(mainTask, cancellationToken); } private static async Task OrWhenCancelled_(this Task mainTask, CancellationToken cancellationToken) { Task cancellationTask = Task.Delay(Timeout.Infinite, cancellationToken); await Task.WhenAny(mainTask, cancellationTask).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); await mainTask; } } 

讨论:

  • 所有解决方案(包括这一个)都没有正确处理原始的ContinueWith指定了TaskScheduler 。 具体来说,考虑一个TaskScheduler创建的TaskScheduler.FromCurrentSynchronizationContext以便在UI场景中使用。 在这种情况下,使用原始的ContinueWith方法,您可以保证在运行委托之前检查了取消令牌,但是在已经进入主线程之后(请参阅此答案 )。 也就是说,旧方法具有在考虑任务结果之前在主线程上“最后一次”检查取消令牌的良好效果(即,胜过主任务是否完成或出现故障)。 这意味着除了使用这些扩展方法之外,新代码必须在try / finally中包装它的await以对CancellationToken最终检查:(。请参阅此问题 。

  • @Noseratio的解决方案可以处理上述问题(如果需要),但它有一个缺点,即要求将继续放在委托中。 在我看来,这失败了转换为使用await一大优势:代码不会在委托中结束,它只是在await并且读取像正常的顺序代码之后。

笔记:

  • 我希望我可以指定空lambda永远不会运行(即代替仅在取消时运行),但.ContinueWith方法不允许这样做。 所以,我(大多是任意选择OnlyOnCancelled)

这个答案来自@Servy来自这个答案 (有修改):

 public static Task WithCancellation(this Task task, CancellationToken token) { return task.ContinueWith(t => t.GetAwaiter().GetResult(), token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } public static Task WithCancellation(this Task task, CancellationToken token) { return task.ContinueWith(t => t.GetAwaiter().GetResult(), token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); }