First look at this simple method:
Public Iterator Function GetLongRunningTasks(count As Long) As IEnumerable(Of Task)
For i = 1 To count
Yield Task.Delay(3000)
Next
End Function
This method returns the specified amount of tasks, each one of which completes 3 seconds after it starts. Let’s call it a simulation of a network API call on an extremely bad network connection (not that it matters).
My problem is that a simple iteration will start the tasks one at a time, so the 3000ms delay happens in between each iteration.
For Each t In GetLongRunningTasks(50)
Await t
Next ' this takes ~150 seconds to complete (50x3000ms)
What I want to do is start all 50 tasks at once, then afterwards enter the foreach-loop. Preferably sticking to the example above, what is the proper way to do this?
EDIT
As suggested by Stephen, one solution is to iterate over GetLongRunningTasks(50).ToList(). Maybe it’s just me but I don’t think it’s at all obvious why ToList is used when I read over the code.
I wonder if the following snippet is exactly the same?
Dim tasks As New List(Of Task)
tasks.AddRange(GetLongRunningTasks(50))
For Each t In tasks
Await t
Next
Just to add some explanation to Stephen’s answer:
GetLongRunningTasks()returns a lazy iterator, which creates theTasks only when you iterate it. In your original code, each iteration creates a singleTask, then waits for it to complete and only then another iteration starts, which starts anotherTask.So, what you need it to first iterate over the whole collection to start all the
Tasks and wait for them to complete only when you have them all. Stephen’sToList()suggestion does exactly that, and yourAddRange()would do the same.If it still isn’t clear to you, maybe one more way to do the same would help:
Also, starting a large number of IO-bound
Tasks most likely isn’t the most efficient option, running them with a limited degree of parallelism is. To do that, you could useSemaphoreSlim‘sWaitAsnyc(), or useActionBlockwithMaxDegreeOfParallelismset from TPL Dataflow.