如何取消 Task.WhenAll?

30

目前我正在使用以下代码等待一组任务完成。但是,现在我有一个情况,我希望能够通过取消令牌来取消/中止WhenAll调用。我该如何解决这个问题?

  Dim TaskCollection As New List(Of Tasks.Task)
  For x As Integer = 1 To Threads
    Dim NewTask As Tasks.Task = TaskHandler.Delegates(DelegateKey).Invoke(Me, Proxies, TotalParams).ContinueWith(Sub() ThreadFinished())
    TaskCollection.Add(NewTask)
  Next

  Await Tasks.Task.WhenAll(TaskCollection)

我假设它将会是下一段代码的类似内容,但是我不确定'XXX'应该填什么。

Await Tasks.Task.WhenAny(Tasks.Task.WhenAll(TaskCollection), XXX)

8
通常,您希望取消实际任务本身。您确定您只想取消对这些任务的等待吗? - Stephen Cleary
这是一个有趣的问题。如果他没有提供大部分答案,我可能解决不了它。 - Jonathan Allen
@StephenCleary 我遇到了一个任务无法完成的问题。目前,我不知道原因。我需要一个故障保护机制,在我决定时取消WaitAll。我知道这并不能解决卡住的任务,但我需要一个快速修复。我想解决主要问题,但我的代码库很大,我不确定它在哪里。由于大小和无法透露客户已付费用的代码,我无法在此处发布完整代码。 - iguanaman
4个回答

34

使用TaskCompletionSource<T>为一些没有异步API的异步条件创建任务。使用CancellationToken.Register将现代的基于CancellationToken的取消系统挂钩到另一个取消系统中。您的解决方案只需要将这两个组合起来。

我在我的AsyncEx库中有一个CancellationToken.AsTask()扩展方法,但您也可以自己编写如下:

<System.Runtime.CompilerServices.Extension> _
Public Shared Function AsTask(cancellationToken As CancellationToken) As Task
  Dim tcs = New TaskCompletionSource(Of Object)()
  cancellationToken.Register(Function() tcs.TrySetCanceled(), useSynchronizationContext := False)
  Return tcs.Task
End Function

使用方法和你期望的一样:

Await Task.WhenAny(Task.WhenAll(taskCollection), cancellationToken.AsTask())

19
一个 cancellationToken.AsTask() 的替代方案是 Task.Delay(Timeout.Infinite, token) - noseratio - open to work
2
当任务没有被取消时该怎么办?您不能将其处理掉。它永远不会完成。在我的代码中,每次用户点击都会启动它。每个用户点击都会创建一个永远不会完成的任务。如果我在令牌上请求取消,那么它就会完成,但这会破坏我的代码流程,因为稍后会使用此令牌来测试是否需要执行其他操作,即使操作未被取消。 - Harry
2
@Harry:你不应该创建永远不会完成的任务。对于现代方法,请参见AsyncEx.Tasks中的Task.WaitAsync - Stephen Cleary
3
@StephenCleary 这个回答现在已经过时,你的库中的代码也已经过时或被删除。如今我该如何取消 Task.WhenAll?也许可以不用你的(预发布)库且不会发生内存泄漏。 - ygoe
3
@Leonardo:我在AsyncEx中有一个Task.WaitAsync(CancellationToken)的扩展方法,现在我建议使用它来取消等待。因此,现在我建议使用await Task.WhenAll(taskCollection).WaitAsync(cancellationToken) - Stephen Cleary
显示剩余7条评论

4
Dim tcs as new TaskCompletionSource(Of Object)()
Await Tasks.Task.WhenAny(Tasks.Task.WhenAll(TaskCollection), tcs)

要取消操作,请调用 tcs.SetResult(Nothing)。这将触发您的 Task.WhenAny。

1
更加优雅,以我的观点来看:

await Task.Run(()=> Task.WaitAll(myArrayOfTasks), theCancellationToken);

谢谢@Artaban! 我们采用了你的建议,并在每个任务中额外检查属性“IsCancellationRequested”,如果是则返回。 - fdhsdrdark
4
WaitAll 是阻塞的。这个问题是关于非阻塞的 WhenAll 的区别。 - Crob
4
这不是正确的。根据文档,Task.Run 的取消标记参数是“可用于取消工作(如果尚未启动)的取消标记。” https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.run?view=net-5.0#System_Threading_Tasks_Task_Run_System_Func_System_Threading_Tasks_Task__System_Threading_CancellationToken_ - foson

1
在.NET 6(或更高版本)中,现在可以使用以下功能:
Await Task.WhenAll(...).WaitAsync(cancellationToken)

WaitAsync 可以接受 TimeSpan timeoutCancellationToken 或两者同时。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接