我使用的API客户端是完全随机的,也就是说,每个操作要么返回Task
,要么返回Task<T>
,例如:
static async Task DoSomething(int siteId, int postId, IBlogClient client)
{
await client.DeletePost(siteId, postId); // call API client
Console.WriteLine("Deleted post {0}.", siteId);
}
使用C# 5的alloc/await操作符,启动多个任务并等待它们全部完成的正确/最有效的方法是什么:
int[] ids = new[] { 1, 2, 3, 4, 5 };
Parallel.ForEach(ids, i => DoSomething(1, i, blogClient).Wait());
或:
int[] ids = new[] { 1, 2, 3, 4, 5 };
Task.WaitAll(ids.Select(i => DoSomething(1, i, blogClient)).ToArray());
由于API客户机在内部使用HttpClient,我希望它立即发出5个HTTP请求,并在每个请求完成时写入控制台。
8条答案
按热度按时间muk1a3rh1#
虽然您与上面的代码并行运行操作,但此代码会阻塞每个操作所运行的每个线程。例如,如果网络调用需要2秒,则每个线程挂起2秒,不做任何事情,但等待。
另一方面,上面的
WaitAll
代码也会阻塞线程,在操作结束之前,线程将无法自由地处理任何其他工作。推荐方法
我更喜欢
WhenAll
,它将以并行方式异步执行您的操作。事实上,在上面的例子中,你甚至不需要
await
,你可以直接从方法返回,因为你没有任何延续:为了支持这一点,这里有一篇详细的博客文章,介绍了所有的替代方案及其优点/缺点:How and Where Concurrent Asynchronous I/O with ASP.NET Web API
yrdbyhpb2#
我很想看看问题中提供的方法的结果以及公认的答案,所以我把它放在测试中。
代码如下:
结果输出:
svgewumm3#
您可以使用
Task.WhenAll
函数,可以向其传递任意数量的任务。Task.WhenAll
返回一个新的任务,当所有的任务都完成时,这个任务就会完成。确保在Task.WhenAll
上异步等待,以避免阻塞UI线程:dfddblmv4#
由于您正在调用的API是Apache,因此
Parallel.ForEach
版本没有多大意义。你不应该在WaitAll
版本中使用.Wait
,因为这会失去并行性。如果调用者是xmlc,另一种选择是在执行Select
和ToArray
之后使用Task.WhenAll
来生成任务数组。第二种选择是使用Rx 2.0vptzau2j5#
Parallel.ForEach
需要一个 user-defined worker列表和一个non-crowdAction
来对每个worker执行操作。Task.WaitAll
和Task.WhenAll
需要List<Task>
,根据定义,它们是异步的。我发现RiaanDP的回应对理解差异非常有用,但需要对
Parallel.ForEach
进行修正。没有足够的声誉来回应他的评论,因此我自己的回应。结果输出如下。执行时间具有可比性。我运行这个测试,而我的电脑是做每周反病毒扫描。改变测试的顺序确实改变了测试的执行时间。
yyhrrdl86#
这个问题是10年前的问题,OP问的是关于C# 5的问题。
从今天起,还有一个选择:
Parallel.ForEachAsync
方法,在.NET 6中引入。下面是一个基于OP代码的示例:
这是完全异步的,不会阻塞任何线程。
另外,
Task.WaitAll
Task.WhenAll
方法,因为它们不限制并行运行的线程数量。因此,如果你有一个巨大的数组,它可以吃掉你所有的RAM。
Parallel.ForEachAsync
允许您指定并行度如下:这样你就只有4个线程并行运行。
xlpyo6sf7#
所有的答案都是运行相同的函数。
下面的代码用于调用不同的函数。只需将常规的
Task.Run()
放入数组中,然后使用Task.WhenAll()
调用:fivyi3re8#
我只是想补充上面所有的答案,如果你写一个库,使用
ConfigureAwait(false)
是一个很好的实践,并获得更好的性能,就像here所说的那样。所以这个片段似乎更好:
一个完整的小提琴链接在这里。