异步任务并行执行

时间:2018-03-10 02:10:43

标签: c# parallel-processing

我正在玩.Net中并行执行任务。我已经实现了下面的函数,它使用Task.WhenAll并行执行任务列表。我还发现我可以使用两个选项在列表中添加任务。选项1是使用Task.Run并传递Func委托。选项2是添加调用的Func委托的结果。

所以我的问题是:

  1. Task.Run(选项1)从线程池中获取其他线程,并通过将它们传递给Task.WhenAll来执行它们中的任务。所以问题是Task.WhenAll是否异步运行列表中的每个任务,以便从线程池中取出使用的线程并将其传递回线程池,或者所有获取的线程都被阻塞,直到执行完成(或引发异常)?
  2. 如果我调用Task.Run传递同步(非等待)或异步(等待)委托,会有什么不同吗?
  3. 在选项2中 - 理论上没有从线程池中获取额外的线程来执行列表中的任务。但是,任务同时执行。 Task.WhenAll是在内部创建线程还是在Task.WhenAll创建的单个线程中执行所有任务? SemaphoreSlim如何影响并发任务?
  4. 您认为处理异步并行任务的最佳方法是什么?

        public static async Task<IEnumerable<TResult>> ExecTasksInParallelAsync<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, Task<TResult>> task, int minDegreeOfParallelism = 1, int maxDegreeOfParallelism = 1)
        {
            var allTasks = new List<Task<TResult>>();
    
            using (var throttler = new SemaphoreSlim(minDegreeOfParallelism, maxDegreeOfParallelism))
            {
                foreach (var element in source)
                {
                    // do an async wait until we can schedule again
                    await throttler.WaitAsync();
    
                    Func<Task<TResult>> func = async () =>
                    {
                        try
                        {
                            return await task(element);
                        }
                        finally
                        {
                            throttler.Release();
                        }
                    };
    
                    //Option 1
                    allTasks.Add(Task.Run(func));
                    //Option 2
                    allTasks.Add(func.Invoke());
                }
    
                return await Task.WhenAll(allTasks);
            }
        }
    

    上述功能以

    执行
      [HttpGet()]
        public async Task<IEnumerable<string>> Get()
        {using (var client = new HttpClient())
            {
                var source = Enumerable.Range(1, 1000).Select(x => "https://dog.ceo/api/breeds/list/all");
                var result = await Class1.ExecTasksInParallelAsync(
                    source, async (x) =>
                    {
                        var responseMessage = await client.GetAsync(x);
    
                        return await responseMessage.Content.ReadAsStringAsync();
                    }, 100, 200);
    
                return result;
            }
    

    }

1 个答案:

答案 0 :(得分:0)

选项2测试得更好

我使用您的代码运行了一些测试,并确定选项2比选项1大约快50倍,至少在我的机器上。但是,使用PLINQ甚至比选项2快10倍。

选项3,PLINQ,甚至更快

你可以用一行PLINQ代替整个混乱:

return source.AsParallel().WithDegreeOfParallelism(maxDegreeOfParallelism)
    .Select( s => task(s).GetAwaiter().GetResult() );

糟糕...选项4

如果task实际上是异步的(我用虚拟同步函数进行测试),那么我的先前解决方案会降低并行性。此解决方案解决了问题:

var tasks = source.AsParallel()
    .WithDegreeOfParallelism(maxDegreeOfParallelism)
    .Select( s => task(s) );
await Task.WhenAll(tasks);
return tasks.Select( t => t.Result );

我在笔记本电脑上运行了10,000次迭代。我做了三次运行以确保没有启动效果。结果:

Run 1
Option 1: Duration: 13727ms
Option 2: Duration: 303ms
Option 3 :Duration: 39ms
Run 2
Option 1: Duration: 13586ms
Option 2: Duration: 287ms
Option 3 :Duration: 28ms
Run 3
Option 1: Duration: 13580ms
Option 2: Duration: 316ms
Option 3 :Duration: 32ms

你可以try it on DotNetFiddle但是你必须使用更短的时间才能保持在配额范围内。

除了允许非常简短且功能强大的代码之外,PLINQ完全杀死它以进行并行处理,如LINQ uses a functional programming approach和功能方法is way better for parallel tasks