C# Task.StartNew()与Parallel.ForEach:多个Web请求场景

C# Task.StartNew()与Parallel.ForEach:多个Web请求场景,c#,multithreading,parallel-processing,task-parallel-library,parallel.foreach,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.ProcessR

我已经阅读了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);
等待任务。whalll(tasks)
来自Scott Hanselman's,据说

“从可伸缩性的角度来看,更好的解决方案是 利用异步I/O。当您跨 在网络上,没有理由(除了方便)阻塞 等待响应返回时的线程“

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

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

考虑到这都是“异步IO”工作,而不是“CPU绑定”工作,而且web请求运行时间不长(最多3秒返回),我倾向于相信现有代码已经足够好了。但这会比Parallel.ForEach提供更好的吞吐量吗?由于分区的原因,Parallel.ForEach可能使用“最小”数量的任务,因此线程的最佳使用(?)。我用一些本地测试对Parallel.ForEach进行了测试,结果似乎没有任何改善

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

感谢您的支持,谢谢

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

在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;
}
如果使用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的限制(您是“处理器时间达到100%”),那么您需要减少CPU的使用。异步IO对此没有任何帮助。如果有什么原因的话,它会导致更多的CPU使用(这里不明显)

对应用程序进行概要分析,以了解什么需要如此多的CPU时间并优化代码

启动并行(并行、任务、异步IO)的方式对并行操作本身的效率没有任何影响。如果以异步方式调用网络,网络不会变得更快。还是一样的硬件。同样,CPU使用率也不低

通过实验确定最佳并行度,并选择适合该并行度的并行技术。如果是几十个,那么线程就完全可以了。如果是在数百个,认真考虑异步IO。< /P> 进行web请求调用(无关,因此可以并行触发)

实际上,您希望同时调用它们,而不是并行调用。也就是说,“同时”,而不是“使用多线程”

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

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

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

然后,所有这些都应该异步完成,而不是使用任务并行或其他并行代码

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

public async Task ProcessRequestAsync(...);
然后,您要做的是使用异步并发(
Task.whalll
),而不是并行并发(
StartNew
/
Run
/
parallel
):


可能我没有提到……实际上,ProcessRequest inturn根据传入的请求(obj)调用HttpClient API的异步版本——PostAsync、SendAsync和GetAsync。将更新问题。仅向任务列表中添加异步函数。不要使用Task.Factory.StartNew。他是CPU受限的。异步IO不会提供更多的吞吐量。他说“考虑到这都是“异步IO”工作,而不是“CPU绑定”工作”,并表示他正在使用HttpClient处理异步web请求。这个CPU是如何绑定的?它是CPU绑定的,因为他将CPU驱动到100%。这限制了他的吞吐量。如果
ProcessRequest
使用异步方法,为什么要在
Task.Factory.StartNew
中调用它?您可以简单地将它返回的任务添加到列表中。如果您实际上是在它内部阻塞,那么在它的某些部分使用异步方法并不重要。最后一个阻塞调用否定了“便利之外”的任何好处,这是一个很好的理由。并行和并发是同义词。在这个答案中使用“parallel”时,您的意思似乎是“多线程”<代码>则所有操作都应异步完成,且不应使用TPL或并行代码。不应使用TPL的
StartNew
Run
;使用TPL来管理表示异步工作的任务是很好的,正如您所展示的那样。你不是在“不使用第三方物流”,你只是在以不同的方式使用它。你是“并行”和“并发”术语。但你对第三方物流的看法是正确的;我的意思是“任务并行”。并行地做事情就是同时做多件事情。通过使用多个线程,或者通过同时执行多个固有的异步操作,您可以同时执行多个操作。这两种操作都会导致并行性。NET中的
Parallel
类的操作都涉及m
tasks.Add(Task.Factory.StartNew(async (object state) => 
{
    await this.ProcessRequestAsync(obj);
}, obj, CancellationToken.None, TaskCreationOptions.AttachedToParent,   TaskScheduler.Default));
public async Task ProcessRequestAsync(...);
await Task.WhenAll(list.Select(x => ProcessRequestAsync(x)));