IO绑定操作的并行执行

时间:2016-02-28 18:13:54

标签: c# task-parallel-library task

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

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

我有两个问题。

  1. 没有多少计算,只是在等待Http请求。在这种情况下,我仍然可以使用Parallel.Foreach()
  2. 如果使用Task代替,那么创建大量代码的最佳做法是什么?假设我使用Task.Factory.StartNew()并将这些任务添加到列表中并等待所有这些任务。是否有一项功能(例如TPL分区程序)可以控制最大任务的数量,并且可以创建最大HttpClient
  3. 在SO上有几个类似的问题,但没有人提到最大值。要求只是使用具有最大HttpClient的最大任务。

    提前谢谢。

2 个答案:

答案 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。但你甚至不需要这样做,所以StartNewTask.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;
相关问题