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

时间:2015-06-05 02:04:54

标签: c# multithreading parallel-processing task-parallel-library parallel.foreach

我已经阅读了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)。

3 个答案:

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