[英]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.