【问题标题】:Awaiting lots of tasks等待大量任务
【发布时间】:2013-07-09 12:29:29
【问题描述】:

我有一组Task(很多,大约 400 个):

IEnumerable<Task> tasks = ...

我想同时运行它们,然后等待它们中的每一个。我使用这段代码来运行任务:

Task.Run(async () => { ... });

每个任务都会自己运行异步方法,这就是为什么我需要 lambda 中的 async 关键字。在这些嵌套任务中,众所周知的是发送的HTTP 请求和接收的HTTP 响应。

我尝试了两种不同的方法来等待所有任务完成:

await Task.WhenAll(tasks);

foreach (var task in tasks)
{
    await task;
}

在我看来,先验的看起来完全一样(但当然它们似乎不一样,否则我一开始就不会在这里发帖......)。

第一种方法使任务运行得更快,但在输出窗口中有大量A first chance exception of type 'System.Net.Sockets.SocketException' occurred in System.dll 和其他类似的方法。此外,在调用await Task.WhenAll() 之后,一些任务仍处于WaitingForActivation 状态。

第二种方式速度较慢,并且它看起来任务没有同时运行(我收到HTTP的响应一个接一个,而等待任务的第一种方式使它们几乎都来了同时)。另外,当我使用foreach 循环等待每个任务并且循环后没有任务具有WaitingForActivation 状态时,我在输出窗口中根本看不到first chance exception

我了解等待一组任务的“最佳”方法是使用WhenAll()(至少为了可读性),但是为什么这两种方法的行为不同?我该如何克服这个问题?理想情况下,我希望任务能够快速运行并确保一切都结束(我在 lambda 中有一个 try catch finally 块来处理服务器错误,并且我没有忘记 finally 中的 if(httpClient != null) httpClient.Dispose()在有人问之前...)。

欢迎任何提示!

编辑

好的,我尝试了另一件事。我补充说:

.ContinueWith(x => System.Diagnostics.Debug.WriteLine("#### ENDED = " + index)));

对于每个任务,indexTask 的编号。 使用foreach 循环时,我得到:

#### ENDED = 0
#### ENDED = 1
#### ENDED = 2
#### ENDED = 3
#### ENDED = 4
...

当使用WhenAll() 时,我得到:

#### ENDED = 1
#### ENDED = 3
#### ENDED = 0
#### ENDED = 4
#### ENDED = 8
...

所以使用foreach 循环使我的所有任务同步运行......这也许可以解释为什么我在输出窗口中没有得到任何First Chance Exception,因为系统根本没有受到算法的压力。

EDIT2:

示例代码:http://pastebin.com/5bMWicD4

它使用这里提供的公共服务:http://timezonedb.com/

【问题讨论】:

  • 它没有让它们同步运行(它们已经在运行),但是你同步得到了结果,并且按照你创建它们的顺序
  • awaitTask 的方式不应影响该任务的执行方式。这真的是您的代码,而不是像foreach (var job in jobs) { await Task.Run(…); } 这样的代码吗?您能否发布一个简短但完整的示例代码来演示这一点?
  • @svick 我在帖子中添加了一个示例代码 :-)

标签: c# multithreading asynchronous async-await c#-5.0


【解决方案1】:

这两种尝试完全不同。

第一次尝试等待所有任务完成,然后继续。只有在所有任务完成后才会抛出。 结果的顺序是不确定的,取决于哪个任务先完成

第二个等待每个任务一个一个,按照它们被放置在任务数组中的顺序,这当然不是你想要的,而且相当慢。即使一个任务失败,它也会异常中止等待。其他任务的结果将丢失。

这并不完全像同步运行任务,因为有些任务会比其他任务更早完成,但您仍然必须一次检查所有任务。

您应该在此处注意Task.WhenAll 不会自行阻止。它返回一个在所有其他任务完成后完成的任务。通过调用await Task.WhenAll,您等待该任务的完成。您可以检查该任务的状态以查看一个或多个子任务是否失败或被取消,或者通过调用 ContinueWith 来处理结果。

您也可以调用 Task.WaitAll 而不是 await Task.WhenAll 来阻止直到所有任务完成,或者其中至少有一个被取消或中止。这与您的第二次尝试有些相似,尽管它仍然避免了一项一项地等待所有任务。

您有很多异常的事实与您等待的方式无关。一次可以对同一个域(即地址)进行多少个 HTTP 连接是有限制的,可能会出现超时错误(通常由连接限制引起)或其他与网络相关的问题。

但是,您收到的异常类型会受到您调用await Task.WhenAll 还是Task.WaitAll 的影响。 This post explains the issue,但简而言之,Task.WaitAll 将收集所有异常并抛出 AggregateException,而 await Task.WhenAll 将只返回其中一个。

顺便问一下,您收到的 SocketException 消息是什么?

【讨论】:

  • 感谢您的回答。如果我做对了,ContinueWith() 中的 lambda 只会在明确等待任务时执行,而不是在任务自行结束时执行?关于First Chance Exception,即使我用trycatch 块包围await WhenAll() 呼叫,我也没有收到任何消息。有没有办法知道幕后发生了什么?
  • “其他任务的结果会丢失。”即使使用Task.WhenAll(),它们也会“丢失”。如果有任何Task 出现故障,则返回的Task 也出现故障。但是您始终可以访问原始的Tasks。
  • 修复了订单。至于例外情况,请查看答案的底部以及指向 Eran Stiller 的帖子的链接
【解决方案2】:

您的代码行为与await 无关。这是由您迭代Tasks 集合的方式引起的。大多数 LINQ 方法都是惰性的,这意味着它们只有在您迭代它们时才会真正执行它们的代码。

因此,此代码仅在前一个完成后才开始每个Task

foreach (var task in tasks)
{
    await task;
}

但这段代码会同时启动所有这些:

foreach (var task in tasks.ToList())
{
    await task;
}

由于Task.WhenAll() 在内部相当于ToList(),因此您将获得与上面第二个sn-p 相同的行为。

【讨论】:

  • 感谢您的回答。还有一点我不明白,我使用了Task.Run,它不会自动运行Task吗?我认为创建Task 并稍后启动它的唯一方法是使用构造函数new Task() 本身。
  • @RedPolygon 是的,但该代码位于不会立即执行的 lambda 中,它仅在您迭代生成的序列时才会执行。
猜你喜欢
  • 1970-01-01
  • 2014-06-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-06
  • 2015-02-02
  • 2016-06-04
  • 1970-01-01
相关资源
最近更新 更多