等待很多任务
本文关键字:多任务 等待 | 更新日期: 2023-09-27 18:22:00
我有一组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)));
对于每个任务,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/
这两种尝试是完全不同的。
第一次尝试等待所有任务完成,然后继续。只有在所有任务完成后,它才会抛出。结果的顺序是不确定的,取决于哪个任务首先完成。
第二个按照它们在任务数组中放置的顺序逐个等待每个任务,这当然不是您想要的,而且相当慢。即使有一个任务失败,它也会中止等待并出现异常。其他任务的结果将丢失。
这与同步运行任务不完全一样,因为某些任务会比其他任务更早完成,但您仍然必须一次检查所有任务。
您应该在此处注意,Task.WhenAll
不会自行阻塞。它返回一个任务,该任务在所有其他任务完成后完成。通过调用await Task.WhenAll
,您将等待该任务的完成。您可以检查该任务的状态以查看一个或多个子任务是否失败或被取消,或者通过调用 ContinueWith 来处理结果。
您还可以调用 Task.WaitAll 而不是 await Task.WhenAll
来阻止,直到所有任务完成,或者至少其中一个任务取消或中止。这有点类似于您的第二次尝试,尽管它仍然避免逐个等待所有任务。
有很多例外的事实与你等待的方式无关。一次可以与同一域(即地址(建立多少个HTTP连接是有限制的,可能会出现超时错误(通常由连接限制引起(或其他与网络相关的问题。
但是,您收到的异常类型受您是调用await Task.WhenAll
还是Task.WaitAll
的影响。这篇文章解释了这个问题,但简而言之,Task.WaitAll
将收集所有异常并抛出 AggregateException,而await Task.WhenAll
只会返回其中一个。
顺便问一下,您收到的套接字异常消息是什么?
你的代码的行为与await
无关。它是由您迭代Task
集合的方式引起的。大多数 LINQ 方法都是惰性的,这意味着它们仅在循环访问时才实际执行其代码。
因此,此代码仅在前一个Task
完成后启动每个:
foreach (var task in tasks)
{
await task;
}
但是这段代码一次启动所有这些:
foreach (var task in tasks.ToList())
{
await task;
}
由于Task.WhenAll()
在内部执行等效于ToList()
,因此您将获得与上述第二个代码段相同的行为。