【问题标题】:Does compiler perform "return value optimization" on chains of async methods编译器是否对异步方法链执行“返回值优化”
【发布时间】:2014-03-21 18:56:03
【问题描述】:

不是传统意义上的返回值优化,但是我想知道你什么时候有这样的情况:

private async Task Method1()
{
    await Method2();
}

private async Task Method2()
{
    await Method3();
}

private async Task Method3()
{
    //do something async
}

这显然可以写得更优化:

private Task Method1()
{
    return Method2();
}

private Task Method2()
{
    return Method3();
}

private async Task Method3()
{
    //do something async
}

我只是想知道是否有人知道 (MS) 编译器是否足够聪明,不会首先为 Method1()Method2() 生成状态机?

【问题讨论】:

  • 我猜不是,因为你仍然可以从其他地方调用方法
  • @knittl:对不起,我不确定你的意思。方法1和2仍然返回可以等待的任务
  • 你为什么不试试呢?编译代码并检查反射器或 ildasm 中的程序集。由于 await 是 C# 编译器功能(而不是 JIT 编译器功能),您可以轻松查看是否存在差异。
  • @cremor:基本上是因为我从来没有时间学习 CIL。是的,我同意我的懒惰,这可能是一个很好的学习练习。
  • 我使用JetBrains's dotPeek,这是一个免费且很棒的工具。重新创建 async/await 语句足够聪明,但也可以查看编译器生成的原始状态机的样子。

标签: c# optimization async-await


【解决方案1】:

不,C# 编译器不会优化它,它不应该。这在概念上是两个不同的东西,这里是similar question

IMO,主要区别在于异常如何传播到 Method1Method2 的调用者中。我演示了这种行为here

在第一种情况下(没有状态机),将立即在调用者的堆栈帧上抛出异常。如果不经处理,应用程序可能会立即崩溃(除非在同一堆栈帧上的调用链中有另一个 async 方法)。

在第二种情况下(使用状态机),异常将在返回给调用者的Task 对象中保持休眠,直到通过await tasktask.Wait() 观察到它,一段时间以后。它可能会在完全不同的堆栈帧上被观察到,或者根本不会被观察到。我发布了有关此here 的更多详细信息。

【讨论】:

  • 这是一个很好的观点,我没有考虑过。任何标记为 async 的方法都会有效地将任何异常编组到返回的 Task 中。谢谢!
【解决方案2】:

你为什么要问一个你可以通过简单的测试在一分钟内回答的问题?

class Program
{
    static void Main(string[] args)
    {
        MainAsync().Wait();
        Console.ReadLine();
    }

    static async Task MainAsync()
    {
        await Method1();
    }

    static async Task Method1()
    {
        await Method2();
    }

    static async Task Method2()
    {
        await Method3();
    }

    static async Task Method3()
    {
        Console.Write("Start");
        await Task.Delay(1000);
        Console.Write("End");
    }
}

这会在 IL 中创建四个不同的状态机。

IL 代码必须是这种方式,因为您可以从任何地方调用方法并且它们的行为必须一致,因此任何优化都必须在 JIT 级别上完成,而不是 C# 编译器。如果您不需要await,请不要使用它 - 这是您的责任。

一个很好的例子是方法重载:

static Task Method()
{
  return Method("Default");
}

static async Task Method(string someString)
{
  await SomeThingAsync(someString);
}

它仍然是异步的,就像您在无参数方法中执行另一个 await 一样 - 但它避免了无用的状态机。

async 关键字的唯一目的是允许您在给定方法内部使用await 关键字。您仍然可以 await 一个不是 async 的方法 - 要求返回 Task,没有 async 关键字。

使用与之前相同的示例,awaits 是多余的。更简单的方法是:

class Program
{
    static void Main(string[] args)
    {
        MainAsync().Wait();
        Console.ReadLine();
    }

    static async Task MainAsync()
    {
        await Method1();
        await Method2();
    }

    static Task Method1()
    {
        return Method2();
    }

    static Task Method2()
    {
        return Method3();
    }

    static async Task Method3()
    {
        Console.Write("Start");
        await Task.Delay(1000);
        Console.Write("End");
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-07-31
    • 1970-01-01
    • 2012-04-21
    • 1970-01-01
    • 2019-10-28
    • 1970-01-01
    • 2012-12-20
    相关资源
    最近更新 更多