与等待继续的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
没有取消的异步模式:
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); }