我已经阅读了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.当你在呼唤时 网络,没有理由(除了方便)阻止 等待响应的线程回来“
现有代码似乎消耗了太多线程,并且处理器时间在生产负载上高达100%,这让我思考。
另一个替代方法是使用Parallel.ForEach,它使用分区程序,但也“阻止”调用,这对我的场景来说很好。
考虑到这是所有“异步IO”工作而不是“CPU绑定”工作,并且Web请求不会长时间运行(最多返回3秒),我倾向于认为现有代码足够好。但是这会提供比Parallel.ForEach更好的吞吐量吗? Parallel.ForEach可能使用“最小”数量的任务,因为分区因此最佳使用线程(?)。我用一些本地测试测试了Parallel.ForEach,看起来没有任何好转。
目标是减少CPU时间并提高吞吐量,从而提高可扩展性。是否有更好的方法来并行处理Web请求?
感谢任何投入,谢谢。
修改 代码示例中显示的ProcessRequest方法确实使用HttpClient及其异步方法来触发请求(PostAsync,GetAsync,PutAsync)。
答案 0 :(得分:5)
进行Web请求调用(不相关,因此可以并行触发)
您真正想要的是将它们称为并发,而不是 parallel 。也就是说,"同时",而不是"使用多个线程"。
是的,我也这么认为。 :)现有代码似乎消耗了太多线程
考虑到这一切都是" Async IO"工作而不是" CPU绑定"工作
然后它应该全部异步完成,不使用任务并行或其他并行代码。
正如Antii指出的那样,你应该让你的异步代码异步:
public async Task ProcessRequestAsync(...);
那么你想要做的是使用异步并发(Task.WhenAll
),而不是并行并发(StartNew
/ {{1 }} / Run
):
Parallel
答案 1 :(得分:3)
如果你受CPU限制(你是 - “处理器时间高达100%”)你需要降低CPU使用率。 Async IO对此没有任何帮助。如果有的话,它会导致更多的CPU使用(这里不明显)。
分析应用程序以查看占用大量CPU时间的内容并优化该代码。
启动并行性的方式(并行,任务,异步IO)对并行操作本身的效率没有任何影响。如果以异步方式调用网络,网络速度会变快。它仍然是相同的硬件。同样不低于CPU使用率。
通过实验确定最佳并行度,并选择适合该程度的并行度技术。如果它是几十个那么线程完全没问题。如果它在数百个中认真考虑异步IO。
答案 2 :(得分:0)
在Task.Factory.StartNew中包装同步调用并不能为您提供异步的任何好处。您应该使用适当的异步函数以获得更好的可伸缩性。请注意Scott Hanselman如何在您引用的帖子中生成异步函数。
例如
public async Task<bool> 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<bool> 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));