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的帖子,上面说

Stephen说,从可伸缩性的角度来看,一个更好的解决方案是利用异步I/O。当你向对岸呼喊对于网络来说,没有理由(除了方便)阻塞线程在等待响应返回"

现有的代码似乎消耗了太多的线程,处理器时间在生产负载上飙升到100%,这让我思考。

另一种选择是使用Parallel。ForEach使用分区but和"block";电话,这对我的场景来说很好。

考虑到这都是"Async "工作而非"CPU边界";工作,和web请求不是长时间运行(最多3秒返回),我倾向于相信现有的代码是足够好的。但是这会提供比Parallel.ForEach更好的吞吐量吗?平行的。ForEach可能会使用"minimal"。由于分区和线程的最佳使用(?)我确实测试了Parallel。

目标是减少CPU时间,提高吞吐量,从而获得更好的可伸缩性。有没有更好的方法来并行处理web请求?

感谢所有的输入,谢谢。

编辑:

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

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

你真正想要的是将它们称为并发的,而不是在parallel中。即"同时",而不是"使用多个线程"。

现有代码似乎占用了太多线程

是啊,我也这么认为。:)

考虑到这都是"异步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使用。

通过实验确定最优并行度,并选择适合该并行度的并行技术。如果是几十个,那么线程是完全没问题的。

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

例如

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方法应该像

一样异步实现
public async Task<bool> ProcessRequestAsync(...)

那么你可以直接

tasks.Add(this.ProcessRequestAsync(obj))

如果你用task . factory . startnew启动task,即使你的ProcessRequest方法在内部进行异步调用,它也不能作为异步工作。如果你想用任务。工厂你应该让你的lambda也是异步的,像

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

最新更新