【问题标题】:Performance gap issue between Task.WaitAll() and execute Task.Wait sequentiallyTask.WaitAll() 和顺序执行 Task.Wait 之间的性能差距问题
【发布时间】:2012-04-22 05:19:43
【问题描述】:

我想在一些同步代码中添加并发,发现过程中存在性能问题,很难理解。

下面代码的运行结果是:

Mission Fibonacci1Async cost 9.4195388 seconds, value 75025
Mission Fibonacci2Async cost 0.2260129 seconds, value 75025

唯一不同的是第二个函数增加了一行await Task.WhenAll(new Task[] { t1, t2 });,让性能提升40倍。

谁能给我解释一下?

    static Task<int> Fibonacci1Async(int n)
    {
        return Task.Run<int>(() => Fibonacci1(n));
    }

    static int Fibonacci1(int n)
    {
        if (n == 0) return 0;
        else if (n == 1) return 1;
        else
        {
            var t1 = Fibonacci1Async(n - 1);
            var t2 = Fibonacci1Async(n - 2);
            return t1.Result + t2.Result;
        }
    }

    static Task<int> Fibonacci2Async(int n)
    {
        return Task.Run<int>(() => Fibonacci2(n));
    }

    static int Fibonacci2(int n)
    {
        if (n == 0) return 0;
        else if (n == 1) return 1;
        else
        {
            var t1 = Fibonacci2Async(n - 1);
            var t2 = Fibonacci2Async(n - 2);

            Task.WaitAll(new Task[] { t1, t2 });
            return t1.Result + t2.Result;
        }
    }

    static void Benchmark(Func<int, Task<int>> func)
    {
        DateTime time = DateTime.Now;
        var task = func(25);
        task.Wait();
        TimeSpan cost = DateTime.Now - time;
        Console.WriteLine("Mission {0} cost {1} seconds value {2}", func.Method.Name, cost.TotalSeconds, task.Result);
    }

    static void Main(string[] args)
    {
        Benchmark(Fibonacci1Async);
        Benchmark(Fibonacci2Async);
        Console.ReadKey();
        return;
    }

【问题讨论】:

    标签: c# multithreading asynchronous concurrency task


    【解决方案1】:

    我怀疑答案与Task.Wait inlining有关。

    在表达式t1.Result + t2.Result 中,+ 运算符从左到右(串行)计算其参数。所以它会阻止t1,然后是t2

    我猜在你的系统上,大部分时间t1 已经启动,但不是t2。在这种情况下,Task.WaitAll 可以将t2“内联”到当前线程池任务中,而不是启动一个新任务,但+ 将阻塞t1

    这只是一个猜测;您应该使用分析器来准确了解发生了什么。

    我无法在我的系统上重现此内容。我总是看到两个版本大致相同,即使对进程应用了处理器亲和性。

    附:命名约定Async 在这里并不真正适用。此代码未使用 async/await - 它使用的是任务并行库。

    【讨论】:

    • 命名约定与await/async无关。这是一个实现细节。它指定调用者接收任务并获得非阻塞行为。
    • 后缀Async 与返回类型相结合意味着该方法使用基于任务的异步模式。我想您可以证明这些 *Async 方法是 TAP,但它们肯定不是 TAP 实现的好的示例。
    • 例如,FileStream.ReadAsync 在内部不使用 await。它使用 IO 完成端口。它没有错。调用者不需要知道。
    • FileStream.ReadAsync 也没有实现基本同步代码的 Task 包装器。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-08
    • 2023-03-18
    • 2018-12-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多