编译器是否执行"返回值优化"在异步方法的链上

时间:2014-03-21 09:24:02

标签: c# optimization async-await

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

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()生成状态机?

2 个答案:

答案 0 :(得分:8)

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

IMO,主要区别在于如何将异常传播到Method1Method2的来电者。我演示了这个行为here

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

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

答案 1 :(得分: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);
}

它仍然像在无参数方法中做另一个等待一样异步 - 但它避免了无用的状态机。

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

使用与以前相同的示例,await是多余的。一个更简单的方法是:

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