首先看看这个简单的方法:
Public Iterator Function GetLongRunningTasks(count As Long) As IEnumerable(Of Task)
For i = 1 To count
Yield Task.Delay(3000)
Next
End Function
此方法返回指定数量的任务,每个任务在启动后 3 秒完成。让我们将其称为在极差的网络连接上进行网络 API 调用的模拟(并不重要)。
我的问题是一个简单的迭代将一次启动一个任务,因此每次迭代之间会发生 3000 毫秒的延迟。
For Each t In GetLongRunningTasks(50)
Await t
Next ' this takes ~150 seconds to complete (50x3000ms)
我想做的是一次启动所有 50 个任务,然后进入 foreach 循环。最好坚持上面的例子,正确的方法是什么?
编辑
正如斯蒂芬所建议的那样,一种解决方案是迭代GetLongRunningTasks(50).ToList()
。也许只有我一个人,但我认为当我阅读代码时为什么使用 ToList 一点也不明显。
我想知道以下片段是否完全相同?
Dim tasks As New List(Of Task)
tasks.AddRange(GetLongRunningTasks(50))
For Each t In tasks
Await t
Next
你可以调用ToList
来创建所有的Task
。然后你可以使用For Each
(如果你只是Await
每一个,Task.WhenAll
)。
只是为了给 Stephen 的答案添加一些解释:GetLongRunningTasks()
返回一个惰性迭代器,它仅在您迭代时创建 Task
s。在您的原始代码中,每次迭代都会创建一个Task
,然后等待它完成,然后才开始另一个迭代,这将启动另一个Task
。
因此,您需要它首先迭代整个集合以启动所有Task
,并仅在拥有所有集合时才等待它们完成。斯蒂芬ToList()
的建议正是这样做的,你的AddRange()
也会这样做。
如果您仍然不清楚,也许还有一种方法会有所帮助:
Dim tasks As New List(Of Task)
For Each t in GetLongRunningTasks(50)
tasks.Add(t)
Next
For Each t In tasks
Await t
Next
此外,启动大量 IO 绑定Task
很可能不是最有效的选择,以有限的并行度运行它们才是。为此,您可以使用SemaphoreSlim
的WaitAsnyc()
,或者将ActionBlock
与TPL数据流中的MaxDegreeOfParallelism
集一起使用。