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

Lal*_*man 8 c# parallel-processing multithreading 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);
Run Code Online (Sandbox Code Playgroud)

await Task.WhenAll(tasks)来自斯科特Hanselman的帖子在那里说,

斯蒂芬说:"从可伸缩性的角度来看,更好的解决方案是利用异步I/O.当你通过网络呼叫时,没有理由(除了方便之外)在等待响应来阻止线程时背部"

现有代码似乎消耗了太多线程,并且处理器时间在生产负载上高达100%,这让我思考.

另一个替代方法是使用Parallel.ForEach,它使用分区器,但也"阻塞"调用,这对我的场景来说很好.

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

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

感谢任何投入.

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

Ste*_*ary 5

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

你真正需要的是给他们打电话的同时,不平行.也就是说,"同时",而不是"使用多个线程".

现有代码似乎消耗了太多线程

是的,我也这么认为.:)

考虑到这是所有"异步IO"工作而不是"CPU绑定"工作

然后它应该全部异步完成,而不是使用任务并行或其他并行代码.

正如Antii指出的那样,您应该使异步代码异步:

public async Task ProcessRequestAsync(...);
Run Code Online (Sandbox Code Playgroud)

那么你想要做的是使用异步并发(Task.WhenAll)而不是并行并发(StartNew/ Run/ Parallel)来使用它:

await Task.WhenAll(list.Select(x => ProcessRequestAsync(x)));
Run Code Online (Sandbox Code Playgroud)

  • 不同意“并行”和“并发”术语。但是你对 TPL 是正确的;我的意思是说“任务​​并行性”。 (2认同)
  • 对于普通英语,我同意他们是同义词.但是,作为开发人员区分并发性,并行性和异步性是有益的.我总是使用并发作为"父"概念,并行和异步描述特定的方法.否则,术语会让IMO感到困惑. (2认同)