代码之家  ›  专栏  ›  技术社区  ›  aspnetuser

调用链中的异步等待分配

  •  2
  • aspnetuser  · 技术社区  · 6 年前

    我试图更好地理解编译器为C#中的async Wait生成的代码所导致的堆分配。

    考虑以下代码:

    static async Task OneAsync()
    {
        Console.WriteLine("OneAsync: Start");
        await TwoAsync();
        Console.WriteLine("OneAsync: End");
    }
    
    static async Task TwoAsync()
    {
        Console.WriteLine("TwoAsync: Start");
        await ThreeAsync();
        Console.WriteLine("TwoAsync: End");
    }
    
    static async Task ThreeAsync()
    {
        Console.WriteLine("ThreeAsync: Start");
        var c = new HttpClient();
        var content = await c.GetStringAsync("http://google.com");
        Console.WriteLine("Content:" + content.Substring(0, 10));
        Console.WriteLine("ThreeAsync: End");
    }
    

    来自ILSpy From ILSpy

    这里有3个 AsyncStateMachine 结构类型(一个用于 OneAsync ,一个用于 TwoAsync 一个用于 ThreeAsync )由编译器生成。

    你能确认一下我的假设是否正确吗?

    • 打电话给 OneAsync 方法(依次调用链,直到 三异步 ),将导致 3. 异步状态机 结构类型 上台 ?

    • 如果我没有在中使用HttpClient 三异步 方法,而只是返回 Task.CompletedTask 从中,将有2个 异步状态机 结构类型(一个用于 OneAsync 一个用于 TwoAsync ).在这种情况下 没有堆分配 异步状态机 整个调用链同步执行时的结构类型?

    2 回复  |  直到 6 年前
        1
  •  4
  •   Jon Skeet    6 年前

    因为有三个异步方法,所以无论哪种方式都有三个状态机。编译器创建的存根 总是 创建状态机。如果你改变,你会有两个 ThreeAsync 没有 async 修饰符,而只需编写 有规律的 返回已完成任务的方法。

    关于堆分配,您是对的:所涉及的等待者在检查时都已完成,因此不需要安排继续。这意味着除了(可能)任务之外,不需要堆分配任何东西。当你回来的时候 Task 而且任务总是会成功的,我希望也会使用缓存完成的任务。

        2
  •  4
  •   Marc Gravell    6 年前

    本质上:两者都是;状态机是生成的,但除非它实际上是异步的,否则不会在堆上结束。在一些性能关键的场景中,大部分调用都是同步的,手动实现代码以在同步和异步之间切换可能是有利的:

    static Task OneAsync()
    {
        async Task Awaited(Task t)
        {
            await t;
            Console.WriteLine("OneAsync: End");
        }
        Console.WriteLine("OneAsync: Start");
        var task = TwoAsync();
        if (task.Status != TaskStatus.RanToCompletion)
            return Awaited(task);
        Console.WriteLine("OneAsync: End");
        return task; // could also have used Task.CompletedTask
    }
    

    请注意,这涉及到一些手动复制,尤其是在结果中或之后发生的事情(即 Console.WriteLine ).有很多方法可以减少这种情况,通常涉及更多的本地功能。还请注意 task.Status 非常昂贵,如果可用(.NET Core或 ValueTask<T> ): IsCompletedSuccessfully 应优先考虑。