在Parallel.Foreach中调用异步方法的最佳实践/正确方法是什么?

时间:2019-07-17 23:19:56

标签: async-await task-parallel-library parallel.foreach

我在.net核心应用程序中需要调用异步方法来处理大量对象。我们使用Parallel.ForEach做到了这一点,因此我们可以利用并行性更快地完成工作。

服务方法是async方法,我们无法更改。我的问题是使用TPL并行时调用此方法的正确方法是什么。

这是一个简化的代码段(出于演示目的,我传递了迭代号而不是对象),以及我们的观察结果:

CallSendAsync方法在内部向API发出HTTP请求(使用HttpClient)。


        private void ParallelFor()
        {
            Parallel.For(0, 100000, i =>
            {
                CallSendAsync(i).GetAwaiter().GetResult();
            });
        }

我上面这段代码的问题是,它使用的是GetAwaiter,它使异步方法同步。但是,以上实现非常快。似乎也可以更有效地管理系统资源。

另一方面,我有这个:


        private void ParallelForWithAsync()
        {
            Parallel.For(0, 100000, async i =>
            {
                await CallSendAsync(i);
            });
        }

此代码具有异步/等待功能。但是它变得非常慢,性能显着下降。它打开了大量的出站端口,最终HTTP请求错误。

第三,我也尝试过:

        private void TaskWaitAll()
        {
            IEnumerable<int> arr = Enumerable.Range(1, 100000);
            Task.WhenAll(
                    arr
                        .Select(CallSendAsync))
                .GetAwaiter()
                .GetResult();
        }

其结果与第二个片段相似。

1 个答案:

答案 0 :(得分:3)

  

我在.net核心应用程序中需要调用异步方法来处理大量对象。我们使用Parallel.ForEach做到了这一点,因此我们可以利用并行性更快地完成工作。

让我在那里停下来。您不会“使用并行”来“使事情变得更快”。并行是一种并发形式(一次执行一件事以上),并且是一种并发形式,它使用多个线程在多核计算机上更快地处理CPU绑定算法。但是,您的操作完全不受CPU的限制。它受I / O限制,这表明Parallel是用于此目的的错误技术。

如果要在基于I / O的处理中同时处理多个项目,则适当的解决方案是使用Task.WhenAll

  

无论速度如何变慢,性能都会大大降低。它打开了大量的出站端口,最终HTTP请求错误。

是的。如果您实际上同时发出十万个HTTP请求,这是可以预期的。请记住,少于IANA ephemeral ports少于16k。对于如此大量的请求,您可能希望将其限制为一个更合理的数目-例如一次20个。 Parallel.For将根据CPU使用率,线程池中的线程数等适当地划分同步工作负载。要限制异步工作,可以使用SemaphoreSlim

private async Task TaskWaitAll()
{
  var mutex = new SemaphoreSlim(20);
  IEnumerable<int> arr = Enumerable.Range(1, 100000);
  var tasks = arr.Select(async i =>
  {
    await mutex.WaitAsync();
    try { await CallSendAsync(i); }
    finally { mutex.Release(); }
  }).ToList();
  await Task.WhenAll(tasks);
}