等待很多任务

时间:2013-07-11 09:33:43

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

我有一组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和其他类似的东西。此外,在调用WaitingForActivation 之后,某些任务仍处于await Task.WhenAll()

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

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

欢迎任何提示!

修改

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

finally

对于每项任务,.ContinueWith(x => System.Diagnostics.Debug.WriteLine("#### ENDED = " + index))); index的编号。 使用Task循环时,我得到:

foreach

使用#### ENDED = 0 #### ENDED = 1 #### ENDED = 2 #### ENDED = 3 #### ENDED = 4 ... 时,我得到:

WhenAll()

因此使用#### ENDED = 1 #### ENDED = 3 #### ENDED = 0 #### ENDED = 4 #### ENDED = 8 ... 循环使我的所有任务同步运行...这可能解释了为什么我在输出窗口中没有得到任何foreach,因为系统没有被算法强调所有

EDIT2:

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

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

2 个答案:

答案 0 :(得分:5)

两次尝试完全不同。

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

第二个等待每个任务逐个,按照它们放在任务数组中的顺序,这当然不是你想要的,而且速度很慢。如果一个任务失败,它将中止等待异常。其他任务的结果将丢失。

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

您应该注意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消息是什么?

答案 1 :(得分:4)

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

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

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

但是此代码会立即启动所有这些代码:

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

由于Task.WhenAll()内部相当于ToList(),因此您将获得与上述第二个代码段相同的行为。