25

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

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

这是第一种方法

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);
        }
    }
}
4

3 回答 3

27

应该有非常小的可辨别的差异。

在您的第一种情况下:

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

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

在你的第二种情况下:

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

The executing thread will fork to the thread pool and (in parallel) start executing Initialize for each element in the client list. Initialize has the same behavior: it immediately queues a method to the thread pool and returns.

The two timings are nearly identical because you're only parallelizing a small amount of code: the queueing of the method to the thread pool and the return of an uncompleted Task.

If Initialize did some longer (synchronous) work before its first await, it may make sense to use AsParallel.

Remember, all async methods (and lambdas) start out being executed synchronously (see the official FAQ or my own intro post).

于 2012-09-24T12:24:20.617 回答
7

有一个独特的主要区别。

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

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

在第二个中,调用将AsParallel在内部使用Task实例来执行 的分区,IEnumerable<T> 并且您将获得Task从 lambda 返回的初始值async p => await p.Initialize()

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

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

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

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

于 2012-09-24T12:22:08.560 回答
0

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

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

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

于 2018-05-15T17:59:47.663 回答