IO绑定操作的并行执行
我已阅读TPL和任务库文档封面。 但是,我仍然无法清楚地理解以下情况,现在我需要实施它。
我会简化我的情况。 我有一个长度为1000的IEnumerable
。我必须使用HttpClient
向它们发出请求。
我有两个问题。
- 没有太多计算 ,只是在等待Http请求。 在这种情况下,我还可以使用
Parallel.Foreach()
吗? - 如果使用
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
。 但你甚至不需要开头,所以StartNew
和Task.Run
都不适合这里。
在SO上有几个类似的问题,但没有人提到最大值。 该要求仅使用具有最大HttpClient的最大任务。
最大值是异步代码真正变得棘手的地方。 使用CPU绑定(并行)代码,解决方案显而易见:您使用与核心一样多的线程。 (好吧,至少你可以从那里开始并根据需要进行调整)。 使用异步代码,没有明显的解决方案。 这取决于很多因素 – 你有多少内存,远程服务器如何响应(速率限制,超时等)等。
这里没有简单的解决方案。 您只需要测试您的特定应用程序如何处理高级别的并发性,然后调整到较低的数字。
我有一些幻灯片 ,试图解释何时适合不同的技术(并行,异步,TPL数据流和Rx)。 如果您更喜欢使用食谱的书面描述,我想您可能会从我的并发书中受益。