IO绑定操作的并行执行

我已阅读TPL和任务库文档封面。 但是,我仍然无法清楚地理解以下情况,现在我需要实施它。

我会简化我的情况。 我有一个长度为1000的IEnumerable 。我必须使用HttpClient向它们发出请求。

我有两个问题。

  1. 没有太多计算 ,只是在等待Http请求。 在这种情况下,我还可以使用Parallel.Foreach()吗?
  2. 如果使用Task ,创建大量的最佳实践是什么? 假设我使用Task.Factory.StartNew()并将这些任务添加到列表中并等待所有这些任务。 是否有一个function(如TPL分区程序)控制最大任务的数量和我可以创建的最大HttpClient

在SO上有几个类似的问题,但没有人提到最大值 。 该要求仅使用具有最大HttpClient的最大任务。

先感谢您。

在这种情况下,我仍然可以使用Parallel.Foreach吗?

这不太合适。 Parallel.Foreach更适用于CPU密集型工作。 它也不支持异步操作。

如果使用Task ,创建大量的最佳实践是什么?

请改用TPL数据流块。 您不会创建大量等待线程可用的任务。 您可以配置最大任务量,并将其重用于同时位于缓冲区中等待任务的所有项目。 例如:

 var block = new ActionBlock( uri => SendRequestAsync(uri), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 50 }); foreach (var uri in uris) { block.Post(uri); } block.Complete(); await block.Completion; 

i3arnon对TPL Dataflow的回答很好; 如果您混合使用CPU和I / O绑定代码,则数据流非常有用。 我将回应他的观点,即Parallel是为CPU绑定代码而设计的; 它不是基于I / O的代码的最佳解决方案, 尤其不适合异步代码。

如果您想要一个适用于大多数I / O代码的替代解决方案 – 并且不需要外部库 – 您正在寻找的方法是Task.WhenAll

 var tasks = uris.Select(uri => SendRequestAsync(uri)).ToArray(); await Task.WhenAll(tasks); 

这是最简单的解决方案,但它确实具有同时启动所有请求的缺点。 特别是如果所有请求都转到相同的服务(或一小组服务),这可能会导致超时。 要解决这个问题,你需要使用某种限制……

是否有一个function(如TPL分区程序)控制最大任务的数量和我可以创建的最大HttpClient?

TPL Dataflow有一个很好的MaxDegreeOfParallelism ,它一次只能启动这么多。 您还可以使用另一个内置函数SemaphoreSlim来限制常规异步代码:

 private readonly SemaphoreSlim _sem = new SemaphoreSlim(50); private async Task SendRequestAsync(Uri uri) { await _sem.WaitAsync(); try { ... } finally { _sem.Release(); } } 

如果使用Task,创建大量的最佳实践是什么? 假设我使用Task.Factory.StartNew()并将这些任务添加到列表中并等待所有这些任务。

你实际上不想使用StartNew 。 它只有一个适当的用例(基于动态任务的并行性),这是非常罕见的。 如果您需要将工作推送到后台线程,现代代码应该使用Task.Run 。 但你甚至不需要开头,所以StartNewTask.Run都不适合这里。

在SO上有几个类似的问题,但没有人提到最大值。 该要求仅使用具有最大HttpClient的最大任务。

最大值是异步代码真正变得棘手的地方。 使用CPU绑定(并行)代码,解决方案显而易见:您使用与核心一样多的线程。 (好吧,至少你可以那里开始并根据需要进行调整)。 使用异步代码,没有明显的解决方案。 这取决于很多因素 – 你有多少内存,远程服务器如何响应(速率限制,超时等)等。

这里没有简单的解决方案。 您只需要测试您的特定应用程序如何处理高级别的并发性,然后调整到较低的数字。


我有一些幻灯片 ,试图解释何时适合不同的技术(并行,异步,TPL数据流和Rx)。 如果您更喜欢使用食谱的书面描述,我想您可能会从我的并发书中受益。