我已阅读TPL和任务库文档封面。但是,我仍然无法清楚地理解以下案例,现在我需要实施它。
我会简化我的情况。我有一个长度为1000的IEnumerable<Uri>
。我必须使用HttpClient
向他们发出请求。
我有两个问题。
Parallel.Foreach()
?Task
代替,那么创建大量代码的最佳做法是什么?假设我使用Task.Factory.StartNew()
并将这些任务添加到列表中并等待所有这些任务。是否有一项功能(例如TPL分区程序)可以控制最大任务的数量,并且可以创建最大HttpClient
?在SO上有几个类似的问题,但没有人提到最大值。要求只是使用具有最大HttpClient的最大任务。
提前谢谢。
答案 0 :(得分:23)
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);
这是最简单的解决方案,但它确实具有同时启动所有请求的缺点。特别是如果所有请求都转到相同的服务(或一小组服务),这可能会导致超时。要解决这个问题,你需要使用某种限制......
是否有一项功能(如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绑定(并行)代码,解决方案显而易见:您使用尽可能多的线程。 (好吧,至少你可以在那里开始并根据需要进行调整)。使用异步代码,没有明显的解决方案。这取决于很多因素 - 你有多少内存,远程服务器如何响应(速率限制,超时等)等。
这里没有简单的解决方案。您只需要测试您的特定应用程序如何处理高级别的并发性,然后调整到较低的数字。
我有一些slides for a talk试图解释何时适当的不同技术(并行,异步,TPL数据流和Rx)。如果您更喜欢使用食谱的书面描述,我认为您可能会从并发my book中受益。
答案 1 :(得分:19)
在这种情况下,我仍然可以使用
Parallel.Foreach
吗?
这不太合适。 Parallel.Foreach
更适合CPU密集型工作。它也不支持异步操作。
如果使用
Task
代替,那么创建大量代码的最佳做法是什么?
请改用TPL数据流块。您不会创建大量等待线程可用的任务。您可以配置最大任务量,并将其重用于同时位于缓冲区中等待任务的所有项目。例如:
var block = new ActionBlock<Uri>(
uri => SendRequestAsync(uri),
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 50 });
foreach (var uri in uris)
{
block.Post(uri);
}
block.Complete();
await block.Completion;