30

Currenly using the following code to wait for a collection of tasks to complete. However, I now have a situation where I want to be able to cancel/abort the WhenAll call, via a cancellation token preferably. How would I go about that?

  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)

I'm assuming it's going to but something along the lines of the next bit of code, but I'm not sure what would go in 'XXX'.

Await Tasks.Task.WhenAny(Tasks.Task.WhenAll(TaskCollection), XXX)
Jacob Lambert
  • 7,449
  • 8
  • 27
  • 47
iguanaman
  • 930
  • 3
  • 13
  • 25
  • 8
    Normally, you would want to cancel the actual tasks themselves. Are you sure you only want to cancel the *waiting* for those tasks? – Stephen Cleary Dec 01 '14 at 22:14
  • It's an interesting problem. I wouldn't have solved it if he hadn't already had most of the answer. – Jonathan Allen Dec 01 '14 at 22:23
  • @StephenCleary I'm having an issue where one of the tasks isn't completing. At the moment, I've no idea why. I need a failsafe to cancel the WaitAll when I decide. I know it doesn't resolve the task that is stuck but need a quick fix. I want to fix the main issue but I have a large code base and I'm not sure where it lies. I can't post full code here due to size and also being unable to reveal code my clients have paid for. – iguanaman Dec 01 '14 at 22:27

3 Answers3

34

Use TaskCompletionSource<T> to create a task for some asynchronous condition that does not already have an asynchronous API. Use CancellationToken.Register to hook the modern CancellationToken-based cancellation system into another cancellation system. Your solution just needs to combine these two.

I have a CancellationToken.AsTask() extension method in my AsyncEx library, but you can write your own as such:

<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

Usage is as you expected:

Await Task.WhenAny(Task.WhenAll(taskCollection), cancellationToken.AsTask())
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 19
    An alternative for `cancellationToken.AsTask()`: `Task.Delay(Timeout.Infinite, token)`. – noseratio Dec 02 '14 at 04:20
  • Marking this as the answer as it allows me to use a linked CancellationToken. – iguanaman Dec 02 '14 at 14:49
  • Nice upgrade. I can see using AsTask in several places. – Jonathan Allen Dec 11 '14 at 04:32
  • 2
    And what to with the task when it's not canceled? You cannot dispose it. It will never complete. In my code I have it started as many times, as the user clicks. Each user click will create a task which will be never completed. It would be, if I request cancellation on the token, but it would break my code flow, because this token is used later to test if other things need to be done when the operation is not canceled. – Harry Nov 01 '16 at 21:54
  • 2
    @Harry: You shouldn't create tasks that never complete. For a modern approach, see `Task.WaitAsync` in [`AsyncEx.Tasks`](https://github.com/StephenCleary/AsyncEx.Tasks). – Stephen Cleary Nov 02 '16 at 01:21
  • 3
    @StephenCleary This answer is now outdated and the code in your library obsolete or removed. How would I cancel a `Task.WhenAll` today? Maybe without your (pre-release) library and without memory leaks. – ygoe Oct 21 '18 at 18:27
  • 1
    @ygoe: [Asked and answered here](https://github.com/StephenCleary/AsyncEx/issues/153). – Stephen Cleary Oct 22 '18 at 14:26
  • @StephenCleary after 7 years, is that still your current recommendation? – Leonardo Mar 28 '21 at 21:46
  • @noseratio altough yours intentions are good (I think), that is a TERRIBLE advice. if that token never gets cancelled, that task will run FOREVER! that's a clear resource leak – Leonardo Mar 28 '21 at 21:49
  • 3
    @Leonardo: I have a `Task.WaitAsync(CancellationToken)` extension method in AsyncEx that I now recommend for canceling waits. So these days I would recommend `await Task.WhenAll(taskCollection).WaitAsync(cancellationToken)`. – Stephen Cleary Mar 28 '21 at 21:50
  • what the name of the package? Nito.AsyncEx? – Leonardo Mar 28 '21 at 21:54
  • Yes, it's [Nito.AsyncEx](https://www.nuget.org/packages/Nito.AsyncEx/). – Stephen Cleary Mar 28 '21 at 21:56
4
Dim tcs as new TaskCompletionSource(Of Object)()
Await Tasks.Task.WhenAny(Tasks.Task.WhenAll(TaskCollection), tcs)

To cancel, call tcs.SetResult(Nothing). This will fire your Task.WhenAny.

Jonathan Allen
  • 68,373
  • 70
  • 259
  • 447
2

More elegant from my opinion :

await Task.Run(()=> Task.WaitAll(myArrayOfTasks), theCancellationToken);
Artaban
  • 89
  • 1
  • Thank you @Artaban! We used your suggestion and additionaly on each task we check the property "IsCancellationRequested" and return if so. – fdhsdrdark Jul 16 '20 at 11:41
  • 3
    WaitAll is blocking. This question is about WhenAll which is non-blocking. https://stackoverflow.com/questions/26564702/what-is-the-difference-between-waitall-and-whenall – Crob Jul 28 '20 at 20:35
  • 3
    This is not correct. According to the docs the Cancellation token parameter to Task.Run is "A cancellation token that can be used to cancel the work if it has not yet started." 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 Sep 23 '21 at 03:57