2013-07-11 123 views
4

我有一组Task(很多很多,大约400个):等待大量的任务

IEnumerable<Task> tasks = ... 

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

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

每个任务的运行异步方法本身,这就是为什么我需要在拉姆达的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响应中的一个,而等待任务的第一种方式让他们来几乎都在同一时间)。另外,当我使用foreach循环等待每个任务并且在循环后没有任何任务具有WaitingForActivation状态时,我在输出窗口中完全看不到first chance exception

我知道等待一组任务的“最佳”方式是使用WhenAll()(至少为了便于阅读),但为什么这两个方法的行为不同?我怎样才能克服这个问题?理想情况下,我希望任务能够快速运行,并确保一切都结束(我有一个trycatchfinally块在lambda中处理服务器错误,我没有忘记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,因为系统根本没有被算法强调。

EDIT2:

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

它使用可以在这里找到一个公共服务:http://timezonedb.com/

+0

这并没有使他们同步运行(他们已经运行),但你得到的结果,同时,并在订货您创建它们 –

+0

在你'await'一个'Task'应该不会影响如何是的方式该任务已执行。这真的是你的代码,而不是像'foreach(var job in jobs){等待Task.Run(...); ''?你可以发布一个简短但完整的示例代码来证明这一点吗? – svick

+0

@svick我在我的文章中添加了一个示例代码:-) – Max

回答

5

的两次尝试是完全不同的。

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

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

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

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

你也可以拨打Task.WaitAll而不是await Task.WhenAll阻塞,直到所有任务完成,或其中至少一个取消或中止。这有点类似于你的第二次尝试,尽管它仍然避免了一个接一个地等待所有任务。

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

您收到的例外情况受到您是否拨打await Task.WhenAllTask.WaitAll的影响。 This post explains the issue,但简而言之,Task.WaitAll将收集所有异常并抛出AggregateException,而await Task.WhenAll将只返回其中的一个。

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

+0

谢谢你的回答。如果我理解正确,只有在任务明确等待时才执行'ContinueWith()'中的lambda,但是当任务自行结束时才执行lambda?关于'第一次机会Exception',我没有得到任何消息,即使我在周围所有的'等待WhenAll()'用''try'块catch'打电话。有没有办法知道幕后发生了什么? – Max

+0

“其他任务的结果将会丢失。”即使使用Task.WhenAll(),它们也会“丢失”。如果任何'任务'故障,则返回的'任务'也出现故障。但是你可以随时访问原始的“任务”。 – svick

+0

“结果的顺序是不确定的,取决于哪个任务先完成。”文档中另有说明:[“返回任务的'Result'将被设置为一个数组,其中包含所有提供的任务的结果* *与提供的顺序相同**。“](http://msdn.microsoft.com/zh-cn/library/hh194874.aspx) – svick

4

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

因此,该代码开头的每个Task后,才上一个完成:

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

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

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

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

+0

谢谢你的回答。还有一些我不明白的地方,我使用了'Task.Run',它不会自动运行'Task'吗?我认为创建'Task'并在稍后启动它的唯一方法是使用构造函数'new Task()'本身。 – Max

+1

@RedPolygon是的,但是该代码位于不立即执行的lambda中,只在迭代结果序列时执行。 – svick