【问题标题】:How do you use AsParallel with the async and await keywords?您如何将 AsParallel 与 async 和 await 关键字一起使用?
【发布时间】:2018-05-16 01:59:47
【问题描述】:

我正在查看某人的异步示例代码,并注意到它的实现方式存在一些问题。在查看代码时,我想知道使用 as 并行循环遍历列表是否比正常循环遍历列表更有效。

据我所知,性能差异很小,两者都用尽了每个处理器,并且都在相同的时间内完成。

这是第一种方法

var tasks= Client.GetClients().Select(async p => await p.Initialize());

这是第二个

var tasks = Client.GetClients().AsParallel().Select(async p => await p.Initialize());

我是否正确假设两者之间没有区别?

完整的程序可以在下面找到

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            RunCode1();
            Console.WriteLine("Here");
            Console.ReadLine();

            RunCode2();
            Console.WriteLine("Here");

            Console.ReadLine();

        }

        private async static void RunCode1()
        {
            Stopwatch myStopWatch = new Stopwatch();
            myStopWatch.Start();

            var tasks= Client.GetClients().Select(async p => await p.Initialize());

            Task.WaitAll(tasks.ToArray());
            Console.WriteLine("Time ellapsed(ms): " + myStopWatch.ElapsedMilliseconds);
            myStopWatch.Stop();
        }
        private async static void RunCode2()
        {
            Stopwatch myStopWatch = new Stopwatch();
            myStopWatch.Start();
            var tasks = Client.GetClients().AsParallel().Select(async p => await p.Initialize());
            Task.WaitAll(tasks.ToArray());
            Console.WriteLine("Time ellapsed(ms): " + myStopWatch.ElapsedMilliseconds);
            myStopWatch.Stop();
        }
    }
    class Client
    {
        public static IEnumerable<Client> GetClients()
        {
            for (int i = 0; i < 100; i++)
            {
                yield return new Client() { Id = Guid.NewGuid() };
            }
        }

        public Guid Id { get; set; }

        //This method has to be called before you use a client
        //For the sample, I don't put it on the constructor
        public async Task Initialize()
        {
            await Task.Factory.StartNew(() =>
                                      {
                                          Stopwatch timer = new Stopwatch();
                                          timer.Start();
                                          while(timer.ElapsedMilliseconds<1000)
                                          {}
                                          timer.Stop();

                                      });
            Console.WriteLine("Completed: " + Id);
        }
    }
}

【问题讨论】:

  • 他们完成了多少时间?
  • RunCode1() 耗时 23244 毫秒,RunCode2() 耗时 23219 毫秒。我有 4 个核心,所以这比我预期的要快一点,但我认为两者之间的时间相当微不足道。
  • 它们非常接近,但我认为如果您在更多客户端(数千个)上尝试它或在Initialize 中添加更长的延迟,您将开始看到差异。 (使用Thread.Sleep(time); 而不是所有秒表代码)
  • 有趣的是,这对时代产生了巨大的影响。 RunCode1() 现在需要 12016,而 RunCode2() 现在需要 6019。

标签: c# asynchronous


【解决方案1】:

应该几乎没有可辨别的差异。

在您的第一种情况下:

var tasks = Client.GetClients().Select(async p => await p.Initialize());

执行线程将(一次一个)开始为客户端列表中的每个元素执行InitializeInitialize 立即将一个方法排入线程池并返回一个未完成的Task

在你的第二种情况下:

var tasks = Client.GetClients().AsParallel().Select(async p => await p.Initialize());

执行线程将分叉到线程池并(并行)开始为客户端列表中的每个元素执行InitializeInitialize 具有相同的行为:它立即将一个方法排队到线程池并返回。

这两个时间几乎相同,因为您只是并行化少量代码:将方法排队到线程池和返回未完成的Task

如果Initialize 在其第一个await 之前做了一些更长的(同步)工作,那么使用AsParallel 可能是有意义的。

请记住,所有 async 方法(和 lambda)一开始都是同步执行的(请参阅 official FAQmy own intro post)。

【讨论】:

    【解决方案2】:

    有一个独特的主要区别。

    在以下代码中,您将自行执行分区。换句话说,您正在从调用GetClients() 返回的IEnumerable&lt;T&gt; 中为每个项目创建一个Task 对象:

    var tasks= Client.GetClients().Select(async p => await p.Initialize());
    

    在第二个中,对AsParallel 的调用将在内部使用Task 实例来执行IEnumerable&lt;T&gt; 的分区并且您将拥有初始的Task从 lambda async p =&gt; await p.Initialize() 返回:

    var tasks = Client.GetClients().AsParallel().
        Select(async p => await p.Initialize());
    

    最后,在这里使用async/await 并没有真正做任何事情。当然,编译器可能会对此进行优化,但您只是在等待一个返回 Task 的方法,然后返回一个通过 lambda 什么都不做的延续。也就是说,由于对Initialize 的调用已经返回Task,因此最好保持简单,然后执行以下操作:

    var tasks = Client.GetClients().Select(p => p.Initialize());
    

    这将为您返回Task 实例的序列。

    【讨论】:

      【解决方案3】:

      要改进上述 2 个答案,这是获得可等待的异步/线程执行的最简单方法:

      var results = await Task.WhenAll(Client.GetClients().Select(async p => p.Initialize()));

      这将确保它旋转单独的线程并最终获得结果。希望对某人有所帮助。我花了很长时间才弄清楚这一点,因为这不是很明显,而且 AsParallel() 函数似乎是你想要的,但不使用 async/await。

      【讨论】:

      • 问题中的代码是已经开始一系列任务,然后在最后等待它们。此外,此代码(以及两个解决方案的问题之一)根本不使用任何额外的线程。并行执行异步操作不涉及任何额外的线程(除非这些特定的异步操作碰巧使用额外的线程来实现,而很多人不会这样做)。
      • 我明白了,但重点是异步,你会按照最初的要求使用 async 和 forall 得到错误。
      • 如果你的意思是 OP 的代码不起作用,那么你应该在你的答案中说出来,因为目前你的答案没有这样说。您的回答只是表明 OP 做了 他们已经在做的事情,这不是一个有效的答案。你没有说它不起作用当然是一件好事,因为它不是真的(至少在这种情况下,你可以构建使用AsParallel 会破坏代码的情况)。
      • AsParallel(async) 会破坏大量代码,因为没有办法等待线程完成,例如 Web 服务。我的解决方案旋转线程(如果需要)并正确等待它们,以便您在没有线程仍在运行的情况下离开函数。因此,我的答案很重要。
      • 非常感谢,有用的评论,不知道为什么它被否决了。解决了我的问题,AsParallel() 不起作用。
      猜你喜欢
      • 2018-09-03
      • 1970-01-01
      • 1970-01-01
      • 2014-03-13
      • 2017-07-02
      • 1970-01-01
      • 1970-01-01
      • 2018-11-21
      • 2017-11-05
      相关资源
      最近更新 更多