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));