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

Task.StartNew() vs Parallel.ForEach : Multiple Web Requests Scenario

我已经通读了 SO 中的所有相关问题,但对触发多个 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 据说

"A better solution from a scalability perspective, says Stephen, is to take advantage of asynchronous I/O. When you're calling out across the network, there's no reason (other than convenience) to blocks threads while waiting for the response to come back"

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

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

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

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

欢迎任何意见,谢谢。

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

在 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 开始任务,即使您的 ProcessRequest 方法在内部进行异步调用,它也不会作为异步工作。如果你想使用 Task.Factory 你应该让你的 lambda 也像这样异步:

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

如果您 CPU 绑定(您是 - "Processor Time shoots up to 100% "),您需要减少 CPU 使用。异步 IO 对此无能为力。如果有的话,它会导致更多 CPU 使用(此处不明显)。

分析应用程序以了解什么占用了这么多 CPU 时间并优化该代码。

启动并行性的方式(并行、任务、异步 IO)对并行操作本身的效率没有任何影响。如果您以异步方式调用它,网络不会变得更快。它仍然是相同的硬件。也不少 CPU 用法。

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

makes the web request calls (unrelated, so could be fired in parallel)

您真正想要的是并发调用它们,而不是并行。即"at the same time",而不是"using multiple threads".

The existing code appears to consume too many threads

嗯,我也这么觉得。 :)

Considering this is all "Async IO" work and not "CPU bound" work

那么应该全部异步完成,不要使用任务并行或其他并行代码。

正如 Antii 所指出的,您应该使您的异步代码成为异步代码:

public async Task ProcessRequestAsync(...);

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

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