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.Factory.StartNew() 并将这些任务添加到列表中并等待所有任务。
你实际上不想使用StartNew
。它只有一种合适的用例(基于动态任务的并行性),这是极其罕见的。现代代码应该使用Task.Run
如果您需要将工作推到后台线程上。但你一开始就不需要它,所以也不需要StartNew
nor Task.Run
放在这里是合适的。
关于 SO 有几个类似的问题,但没有人提到最大值。要求只是使用最多的任务和最多的 HttpClient。
最大值是异步代码真正变得棘手的地方。对于受 CPU 限制的(并行)代码,解决方案是显而易见的:您可以使用与内核一样多的线程。 (好吧,至少你可以start并根据需要进行调整)。对于异步代码,没有那么明显的解决方案。这取决于很多因素——你有多少内存、远程服务器如何响应(速率限制、超时等)等等。
这里没有简单的解决方案。您只需测试您的特定应用程序如何处理高级别并发性,然后限制到较低的数字。
我有一些演讲幻灯片 https://github.com/StephenCleary/Presentations/tree/a406324f894da3ca50676e7b3f5bd40dc3078061/Asynchronous%20Parallel%20Reactive%20-%20HELP!试图解释不同技术何时适用(并行、异步、TPL 数据流和 Rx)。如果您更喜欢更多带有食谱的书面描述,我认为您可能会受益my book http://stephencleary.com/book/关于并发。