4

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

IEnumerable<Task> tasks = ...

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

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

每个任务都会自己运行异步方法,这就是为什么我需要asynclambda 中的关键字。在这些嵌套任务中,有众所周知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我在输出窗口中根本看不到任何内容。foreachWaitingForActivation

我理解等待一组任务的“最佳”方式是使用WhenAll()(至少为了可读性),但为什么这两种方法的行为不同?我该如何克服这个问题?理想情况下,我希望任务能够快速运行并确保一切都结束(我try catch finally在 lambda 中有一个块来处理服务器错误,而且我没有忘记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循环使我所有的任务同步运行......这也许可以解释为什么我在输出窗口中没有得到任何东西First Chance Exception,因为系统根本没有受到算法的压力。

编辑2:

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

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

4

2 回答 2

5

这两种尝试完全不同。

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

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

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

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

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

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

但是,您收到的异常类型会受到您调用await Task.WhenAllTask.WaitAll. 这篇文章解释了这个问题,但简而言之,Task.WaitAll它将收集所有异常并抛出一个 AggregateException 而await Task.WhenAll只会返回其中一个。

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

于 2013-07-11T10:00:42.000 回答
4

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

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

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

但是这段代码一次启动了所有这些:

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

并且由于Task.WhenAll()与内部等效ToList(),您将获得与上面第二个片段相同的行为。

于 2013-07-12T09:51:37.900 回答