[英]How to safely cancel a task using a CancellationToken and await Task.WhenAll
我有一个框架,它创建一个CancellationTokenSource,配置CancelAfter,然后调用一个异步方法并传递令牌。 然后,异步方法产生许多任务,将取消令牌传递给每个任务,然后等待任务收集。 这些任务每个都包含通过轮询IsCancellationRequested正常取消的逻辑。
我的问题是,如果我将CancellationToken传递给Task.Run(),则会引发包含TaskCanceledException的AggregateException。 这样可以防止任务正常取消。
为了解决这个问题,我无法将CancellationToken传递给Task.Run,但是我不确定会丢失什么。 例如,我喜欢这样的想法,即如果我的任务挂起并且无法执行优雅的取消,则此异常将迫使它退出。 我当时想我可以使用两个CancellationToken来处理这个问题,一个是“优雅”的,另一个是“强制”的。 但是,我不喜欢这种解决方案。
这是一些代表我上面描述的伪代码。
public async Task Main()
{
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(30000);
await this.Run(cts.Token);
}
public async Task Run(CancellationToken cancelationToken)
{
HashSet<Task> tasks = new HashSet<Task>();
foreach (var work in this.GetWorkNotPictured)
{
// Here is where I could pass the Token,
// however If I do I cannot cancel gracefully
// My dilemma here is by not passing I lose the ability to force
// down the thread (via exception) if
// it's hung for whatever reason
tasks.Add(Task.Run(() => this.DoWork(work, cancelationToken))
}
await Task.WhenAll(tasks);
// Clean up regardless of if we canceled
this.CleanUpAfterWork();
// It is now safe to throw as we have gracefully canceled
cancelationToken.ThrowIfCancellationRequested();
}
public static void DoWork(work, cancelationToken)
{
while (work.IsWorking)
{
if (cancelationToken.IsCancellationRequested)
return // cancel gracefully
work.DoNextWork();
}
}
我建议您遵循引发异常的标准取消模式,而不仅仅是返回:
public static void DoWork(work, cancellationToken)
{
while (work.IsWorking)
{
cancellationToken.ThrowIfCancellationRequested();
work.DoNextWork();
}
}
如果您有清理工作要做,那就是finally
目的(或者using
,如果可以重构的话):
public async Task Run(CancellationToken cancellationToken)
{
HashSet<Task> tasks = new HashSet<Task>();
foreach (var work in this.GetWorkNotPictured)
{
tasks.Add(Task.Run(() => this.DoWork(work, cancellationToken))
}
try
{
await Task.WhenAll(tasks);
}
finally
{
this.CleanUpAfterWork();
}
}
除了将CancellationToken
传递给Task.Run
,还提供它。 当您执行此Task.Run
可以看到引发的异常是由它所给出的CancellationToken
引起的,并将该Task
标记为已取消。
tasks.Add(Task.Run(() => this.DoWork(work, cancelationToken),
cancelationToken));
完成此操作后,您可以确保在取消令牌时DoWork
引发,而不是检查IsCancellationRequested
尝试通过被标记为“成功完成”来结束。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.