Task.StartNew()vs Parallel.ForEach:多个Web请求场景

我已经阅读了SO中的所有相关问题,但对于我的场景中触发多个Web服务调用的最佳方法有点困惑。

我有一个聚合器服务,它接受输入,解析并将其转换为多个Web请求,进行Web请求调用(不相关,因此可以并行触发)并合并发送回调用者的响应。 现在使用以下代码 –

list.ForEach((object obj) => { tasks.Add(Task.Factory.StartNew((object state) => { this.ProcessRequest(obj); }, obj, CancellationToken.None, TaskCreationOptions.AttachedToParent, TaskScheduler.Default)); }); await Task.WhenAll(tasks); 

await Task.WhenAll(tasks)来自Scott Hanselman的post ,据说

斯蒂芬说:“从可扩展性的角度来看,更好的解决方案是利用异步I / O.当你通过网络呼叫时,没有理由(除了方便之外)在等待响应来阻止线程时背部”

现有代码似乎消耗了太multithreading,并且处理器时间在生产负载上达到100%,这让我思考。

另一个替代是使用Parallel.ForEach,它使用分区器,但也“阻塞”调用,这对我的场景来说很好。

考虑到这是所有“异步IO”工作而不是“CPU绑定”工作,并且Web请求不会长时间运行(最多返回3秒),我倾向于认为现有代码足够好。 但这会提供比Parallel.ForEach更好的吞吐量吗? Parallel.ForEach可能使用“最小”数量的任务,因为分区因此最佳使用线程(?)。 我用一些本地测试测试了Parallel.ForEach,但似乎没有更好。

目标是减少CPU时间并提高吞吐量,从而提高可扩展性。 是否有更好的方法来并行处理Web请求?

感谢任何投入。

编辑:代码示例中显示的ProcessRequest方法确实使用HttpClient及其异步方法来触发请求(PostAsync,GetAsync,PutAsync)。

进行Web请求调用(不相关,因此可以并行触发)

你真正想要的是同时调用它们,而不是并行调用它们。 也就是说,“同时”,而不是“使用多个线程”。

现有代码似乎消耗了太multithreading

是的,我也这么认为。 🙂

考虑到这是所有“异步IO”工作而不是“CPU绑定”工作

然后它应该全部异步完成,而不是使用任务并行或其他并行代码。

正如Antii指出的那样,您应该使异步代码异步:

 public async Task ProcessRequestAsync(...); 

那么你想要做的是使用异步并发Task.WhenAll ),而不是并行并发StartNew / Run / Parallel ):

 await Task.WhenAll(list.Select(x => ProcessRequestAsync(x))); 

如果你受CPU限制(你是 – “处理器时间高达100%”)你需要降低CPU使用率。 Async IO没有任何帮助。 如果有什么它会导致更多的CPU使用(这里不明显)。

分析应用程序以查看占用大量CPU时间的内容并优化该代码。

启动并行性的方式(并行,任务,异步IO)对并行操作本身的效率没有任何影响。 如果以异步方式调用网络,网络速度不会更快。 它仍然是相同的硬件。 同样不低于CPU使用率。

通过实验确定最佳并行度,并选择适合该程度的并行度技术。 如果它是几十个那么线程完全没问题。 如果它在数百个认真考虑async IO。

在Task.Factory.StartNew中包装同步调用不会给你任何异步的好处。 您应该使用适当的异步函数以获得更好的可伸缩性。 请注意Scott Hanselman如何在您引用的post中生成异步函数。

例如

 public async Task ValidateUrlAsync(string url) { using(var response = (HttpWebResponse)await WebRequest.Create(url).GetResponseAsync()) return response.StatusCode == HttpStatusCode.Ok; } 

结帐http://blogs.msdn.com/b/pfxteam/archive/2012/03/24/10287244.aspx

因此,您的ProcessRequest方法应该像async一样实现

 public async Task ProcessRequestAsync(...) 

那么你可以

 tasks.Add(this.ProcessRequestAsync(obj)) 

如果使用Task.Factory.StartNew启动任务,即使您的ProcessRequest方法在内部进行异步调用,它也不会像异步一样工作。 如果你想使用Task.Factory,你应该让你的lambda也像async一样:

 tasks.Add(Task.Factory.StartNew(async (object state) => { await this.ProcessRequestAsync(obj); }, obj, CancellationToken.None, TaskCreationOptions.AttachedToParent, TaskScheduler.Default));