C# Task.StartNew()与Parallel.ForEach:多个Web请求场景
我已经阅读了SO中的所有相关问题,但是对于触发多个web服务调用的场景的最佳方法有点困惑 我有一个聚合器服务,它接收输入,解析并将其转换为多个web请求,进行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
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)));