并行启动迭代器方法返回的任务



首先看看这个简单的方法:

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很可能不是最有效的选择,以有限的并行度运行它们才是。为此,您可以使用SemaphoreSlimWaitAsnyc(),或者将ActionBlock与TPL数据流中的MaxDegreeOfParallelism集一起使用。

最新更新