我有这种情况:
private Task LongRunningTask = /* Something */;
private void DoSomethingMore(Task previousTask) { }
public Task IndependentlyCancelableSuccessorTask(CancellationToken cancellationToken)
{
return LongRunningTask.ContinueWith(DoSomethingMore, cancellationToken);
}
特别是,我在此感兴趣的行为在MSDN's page about Continuation Tasks中通过以下术语详细说明:
在这些情况下,延续会进入
Canceled
状态:
- [...]
- 当继续传递时,
System.Threading.CancellationToken
作为参数传递,并且在继续运行之前,令牌的IsCancellationRequested
属性为true
。在这种情况下,延续不会开始,它会转换为Canceled
状态。
上面的代码有效。但是,我正在尽可能多地使用await
关键字转换我的延续。
是否存在使用await
的等效项,允许在等待任务完成之前取消继续?
答案 0 :(得分:6)
以下应该这样做,虽然看起来有点尴尬:
private Task LongRunningTask = /* Something */;
private void DoSomethingMore() { }
public async Task IndependentlyCancelableSuccessorTask(
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var tcs = new TaskCompletionSource<bool>();
using (cancellationToken.Register(() => tcs.TrySetCanceled()))
await Task.WhenAny(LongRunningTask, tcs.Task);
cancellationToken.ThrowIfCancellationRequested();
DoSomethingMore();
}
[更新] 根据svick的建议,根据Stephen Toub的Implementing Then with Await模式,这里形成了一个帮手:
public static class TaskExt
{
/// <summary>
/// Use: await LongRunningTask.Then(DoSomethingMore, cancellationToken)
/// </summary>
public static async Task Then(
this Task antecedent, Action continuation, CancellationToken token)
{
await antecedent.When(token);
continuation();
}
/// <summary>
/// Use: await LongRunningTask.When(cancellationToken)
/// </summary>
public static async Task When(
this Task antecedent, CancellationToken token)
{
token.ThrowIfCancellationRequested();
var tcs = new TaskCompletionSource<Empty>();
using (token.Register(() => tcs.TrySetCanceled()))
await Task.WhenAny(antecedent, tcs.Task);
token.ThrowIfCancellationRequested();
}
struct Empty { };
}
也许,第一个ThrowIfCancellationRequested()
是多余的,但我没有彻底考虑所有边缘情况。
答案 1 :(得分:3)
虽然这个答案在概念上与Noseratio相同,但我对实现的一些细节并不满意,因此我发布了我提议的帮助程序实现,以便其他人就此问题发表评论。
public static async Task<TResult> WhenNotCanceled<TResult>(this Task<TResult> mainTask, CancellationToken cancellationToken)
{
if (!cancellationToken.CanBeCanceled) {
return await mainTask.ConfigureAwait(false);
}
cancellationToken.ThrowIfCancellationRequested();
Task<TResult> completedTask;
var cancellationTaskSource = new TaskCompletionSource<TResult>();
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<object>();
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();
}
答案 2 :(得分:1)
我的答案与@Jean Hominal的答案略有不同,并且还采纳了@Noseratio的方法:
public static class TaskExtensionMethods
{
public static Task<TResult> OrWhenCancelled<TResult>(this Task<TResult> mainTask, CancellationToken cancellationToken)
{
if (!cancellationToken.CanBeCanceled)
return mainTask;
return OrWhenCancelled_(mainTask, cancellationToken);
}
private static async Task<TResult> OrWhenCancelled_<TResult>(this Task<TResult> 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
的情况。具体来说,考虑在UI场景中使用的TaskScheduler创建TaskScheduler.FromCurrentSynchronizationContext
。在这种情况下,使用原始ContinueWith
方法,您可以确保在运行委托之前检查了取消令牌,但是在已经进入主线程之后(请参阅this answer)。也就是说,旧方法具有在考虑任务结果之前在主线程上“最后一次”检查取消令牌的良好效果(即,胜过主任务是否完成或出现故障)。这意味着除了使用这些扩展方法之外,新代码必须将其await
包装在try / finally中以对CancellationToken
进行最终检查:(。请参阅this question。< / p>
@ Noseratio的解决方案可以处理上述问题(如果需要),但它有一个缺点,即要求将继续放在委托中。在我看来,这失败了转换为使用await
的一大优势:代码不会在委托中结束,它只是在await
之后,并且读取像普通的顺序代码。 / p>
注意:
答案 3 :(得分:0)
这个答案来自this answer的@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<T> WithCancellation<T>(this Task<T> task,
CancellationToken token)
{
return task.ContinueWith(t => t.GetAwaiter().GetResult(), token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}