C#使用任务异步编程比同步编程更慢

10

你知道为什么同步斐波那契方法比异步/等待更快,而异步任务更快吗?

我在每个项目方法中使用异步,所以这是一个不好的方法...

代码:

        static int FibonacciSync(int number)
        {
            if (number == 0) { return 0; }
            else if (number == 1) { return 1; }
            else
            {
                var t1 = FibonacciSync(number - 1);
                var t2 = FibonacciSync(number - 2);
                return t1 + t2;
            }
        }

        async static Task<int> FibonacciAwait(int number)
        {
            if (number == 0) 
            { return 0; }
            else if (number == 1) 
            { return 1; }
            else
            {
                var task1 = FibonacciAwait(number - 1);
                var task2 = FibonacciAwait(number - 2);
                return (await task1) + (await task2);
            }
        }

        static Task<int> FibonacciAsync(int number)
        {
            if (number == 0) 
            { return Task.FromResult(0); }
            else if (number == 1) 
            { return Task.FromResult(1); }
            else
            {
                return FibonacciAsync(number - 1).ContinueWith(task1 =>
                {
                    return FibonacciAsync(number - 2).ContinueWith(task2 =>
                    {
                        return task1.Result + task2.Result;
                    });
                }).Unwrap();
            }
        }

结果:

  • 同步: 00:00:00.0121900
  • 等待: 00:00:00.2118170
  • 异步: 00:00:02.6211660

3
await Task 带来了一些额外的开销:捕获上下文,将 lambda 分配给线程池,在上下文中进行切换。这是一种权衡。在您的情况下,额外的开销占优势。 - Dmitry Bychenko
7
异步不是关于速度,而是关于响应性。 - Daniel Mann
2
你是如何获取这些数字的?基准测试JIT代码是出了名的困难。此外,return t1 + t2 不适合异步操作。 - Dour High Arch
2个回答

43
您知道为什么同步斐波那契方法比异步/等待方法更快,而异步任务又更快吗?
异步不是为了提高原始速度。正如您发现的那样,它总体上需要更长时间。如果使用不当(就像您所做的那样),它会使事情变得更慢,并且没有任何好处。
根本问题在于您不理解异步的用途。异步是用来管理延迟的。一旦您内化了这个事实,就会开始正确使用它。
延迟是请求计算或副作用与计算或副作用完成之间的时间差。例如,假设您正在计算一些非常耗费计算资源的东西。例如,您正在计算一个非常复杂的图形,即使您将整个核心专用于此,也需要超过30毫秒才能计算出来。您不希望用户界面在计算时停顿,因此可以将计算放到另一个线程中,将CPU专用于该线程,并等待结果。等待意味着“在我等待高延迟操作完成时找更多工作要做”。例如,假设您正在进行的操作不需要大量计算资源,但需要等待外部资源。例如,您正在调用数据库,并且需要超过30毫秒才能获取结果。在这种情况下,您不希望启动一个线程。该线程只会因为等待结果而进入休眠状态!相反,您应该使用数据库访问API的异步版本,并等待高延迟的结果
在您的示例中,您根本没有高延迟操作,因此等待它是没有意义的。那样只会为运行时创建和管理工作队列带来很多额外工作,而您却得不到任何好处。您的工作只需要纳秒级别的时间;只有在需要毫秒或更长时间的作业时才使用异步处理。 仅在存在高延迟操作时才使用异步处理。异步处理可以提高性能,因为它可以释放一个线程,使其在等待高延迟结果出现时继续执行其他任务。如果没有高延迟结果,异步处理将只会减慢所有操作。

4
一个好的经验法则是:仅在使用外部资源(如文件系统、数据库、http调用等)的函数中使用异步调用。
当您进行内存计算(如计算斐波那契数列)时,请同步进行,为内存计算创建单独的线程/上下文的开销太大了。
除非您有一个UI线程正在等待计算完成,否则您可以实现它异步,但请注意,您确实会有一些性能损失...

那么,当我在数据库层有异步方法时,我必须异步实现 API 方法吗? - Mohsen Samiei
1
通常也会将其用于大型操作,例如在图像或大型数据集上进行操作,否则会使用户界面挂起。 - VoteCoffee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接