异步等待性能?

时间:2014-05-26 13:55:20

标签: c# performance async-await theory

(只是一个理论问题 - 对于非gui应用程序)

假设我的代码包含许多awaits

public async Task<T> ConsumeAsync()
    {
          await A();
          await b();
          await c();
          await d();
          //..
    }

每项任务可能需要很短的时间,

问题(再次,理论上)

可能 整个时间处理所有那些“释放回线程”和“取回线程”(红色和绿色在这里:) < / p>

enter image description here

正在花费更多时间而不是单个线程,这可以通过少量延迟完成所有工作,

我的意思是,我想成为最富有成效的人,但是,因为所有这些来回切换 - 实际上我失去了生产力。

这种情况会发生吗?

5 个答案:

答案 0 :(得分:20)

是的,从理论上讲。通常情况下,在现实世界中。

在常见情况下,async用于I / O绑定操作,与它们相比,线程管理的开销无法检测到。大多数情况下,异步操作要么花费很长时间(与线程管理相比),要么已经完成(例如,高速缓存)。请注意async有一条&#34;快速路径&#34;如果操作已经完成,则不会产生线程。

有关详细信息,请参阅Zen of AsyncAsync Performance

答案 1 :(得分:14)

Task对象表示待处理操作的延迟结果。如果您没有任何待处理操作,则不必使用任务和async/await。否则,我认为async / await代码通常比其纯粹的TPL ContinueWith模拟更有效。

让我们做一些时间:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication
{
    class Program
    {
        // async/await version
        static async Task<int> Test1Async(Task<int> task)
        {
            return await task;
        }

        // TPL version
        static Task<int> Test2Async(Task<int> task)
        {
            return task.ContinueWith(
                t => t.Result,
                CancellationToken.None,
                TaskContinuationOptions.ExecuteSynchronously,
                TaskScheduler.Default);
        }

        static void Tester(string name, Func<Task<int>, Task<int>> func)
        {
            var sw = new System.Diagnostics.Stopwatch();
            sw.Start();
            for (int i = 0; i < 10000000; i++)
            {
                func(Task.FromResult(0)).Wait();
            }
            sw.Stop();
            Console.WriteLine("{0}: {1}ms", name, sw.ElapsedMilliseconds);
        }

        static void Main(string[] args)
        {
            Tester("Test1Async", Test1Async);
            Tester("Test2Async", Test2Async);
        }
    }
}

输出:

Test1Async: 1582ms
Test2Async: 4975ms

因此,默认情况下,await延续比ContinueWith延续更有效。让我们稍微优化一下这段代码:

// async/await version
static async Task<int> Test1Async(Task<int> task)
{
    if (task.IsCompleted)
        return task.Result;
    return await task;
}

// TPL version
static Task<int> Test2Async(Task<int> task)
{
    if (task.IsCompleted)
        return Task.FromResult(task.Result);

    return task.ContinueWith(
        t => t.Result,
        CancellationToken.None,
        TaskContinuationOptions.ExecuteSynchronously,
        TaskScheduler.Default);
}

输出:

Test1Async: 1557ms
Test2Async: 429ms

现在非异步版本获胜。对于async版本,我认为此优化已由async/await基础架构在内部完成。

无论如何,到目前为止,我们只处理完成的任务(Task.FromResult)。让我们介绍一下实际的异步性(当然,这次我们会做更少的迭代):

static Task<int> DoAsync()
{
    var tcs = new TaskCompletionSource<int>();
    ThreadPool.QueueUserWorkItem(_ => tcs.SetResult(0));
    return tcs.Task;
}

static void Tester(string name, Func<Task<int>, Task<int>> func)
{
    ThreadPool.SetMinThreads(200, 200);
    var sw = new System.Diagnostics.Stopwatch();
    sw.Start();
    for (int i = 0; i < 1000000; i++)
    {
        func(DoAsync()).Wait();
    }
    sw.Stop();
    Console.WriteLine("{0}: {1}ms", name, sw.ElapsedMilliseconds);
}

输出:

Test1Async: 4207ms
Test2Async: 4734ms

现在差异很小,尽管async版本仍然表现稍好一些。然而,我认为这种收益实际上是可以忽略的,与异步操作的实际成本或恢复SynchronizationContext.Current != null时捕获的上下文的成本相当。

如果您处理异步任务,最重要的是,如果您有选择,请选择async / await,不是出于性能原因,而是为了易用性,可读性和可维护性。

答案 2 :(得分:2)

  

会出现这种情况吗?

绝对。因此,您应该认真对待使用异步代码的位置。通常,您最好将其用于实际执行异步操作的方法(例如,磁盘或网络I / O)。这些操作所花费的时间通常远远超过在线程上调度任务的成本。此外,在操作系统级别,这些类型的操作本质上是异步的,因此您实际上通过使用异步方法删除抽象层。

即使在这些情况下,除非您能够利用并发性,否则通过切换到异步代码可能看不到明显的性能差异。例如,您发布的代码可能看不到实际的性能提升,除非它被改为这样的东西:

await Task.WhenAll(new[]{A(), B(), C(), D(), ...});

答案 3 :(得分:2)

是的,它可能发生。另外不要忘记 - 你可以编程的所有效率 - 任务系统都有开销。

如果你对这样的事情太过粗暴,那么同步开销会杀了你。这意味着:任务编程非常有效。

但旧规则坚持:不要超级精细化。 SOmetimes优化有帮助。

答案 4 :(得分:1)

是的,当然可以发生。利用创建状态机的所有开销,来回控制并使用IOCP线程。但如上所述,TPL已经过优化。例如,不要忘记,如果TaskAwaitable快速完成,则可能没有开销,并且它将同步执行,这可能通常在快速操作时发生。