揭开.NET 中 async/await 的神秘面纱:隐藏代价与优化之道

作为资深的.NET 开发人员,我们都曾将 async/await 用作处理异步操作的常用模式。它简洁、直观,并且使我们的代码更易于维护。然而,在这种优秀的语法背后,隐藏着一套复杂的机制,一旦被误用,可能会对应用程序的性能产生重大影响。

本文将揭示其中隐藏的代价,并探讨每一位经验丰富的开发人员都应该了解的优化策略。

理解基础原理 .NET 中的 async/await 模式从根本上改变了我们编写异步代码的方式。在学习高级模式之前,让我们先了解一下当我们编写异步代码时,底层会发生什么。

异步状态机流程… 幕后实际发生了什么? 当你将一个方法标记为 async 时,.NET 会进行一些有趣的操作。它会获取你的代码,并将其转换为一种特殊的结构,称为 “状态机”。

可以把它想象成将你的代码分解成更小的部分,这些部分可以暂停和恢复。

是的,你编写的代码是这样的:

代码语言:javascript代码运行次数:0运行复制
public async Task<int> ProcessOrderAsync()
{
    var data = await GetDataAsync();       // 步骤 1
    var result = await ProcessDataAsync(data); // 步骤 2
    return result;
}

但是,它实际变成的样子(简化后)是:

代码语言:javascript代码运行次数:0运行复制
public Task<int>ProcessOrderAsync()
{
    // 创建一个结构来跟踪我们当前的位置
    var stateMachine =newAsyncStateMachine();
    
    // 存储任何局部变量
    stateMachine.data =null;
    stateMachine.result =;
    
    // 开始处理
    Start(stateMachine);
    
    // 返回一个最终会包含结果的 Task
    return stateMachine.Task;
}

为什么这很重要(对性能的影响) 这种转换会带来一些代价:

  1. 内存使用:每个异步方法都需要额外的内存来:
    • 存储状态机
    • 跟踪局部变量
    • 创建 Task 对象
  2. 速度:会有一些额外的工作要做:
    • 设置状态机
    • 在代码的不同部分之间切换
    • 管理所有这些部分

看看这个…

代码语言:javascript代码运行次数:0运行复制
// 简单但可能会浪费资源
publicasyncTask<int>GetValueAsync()
{
    returnawait Task.FromResult();
}

// 对于简单情况更高效
publicTask<int>GetValueBetter()
{
    return Task.FromResult();
}

在第一个版本中,我们创建了一个实际上并不需要的状态机。第二个版本更高效,因为它直接返回了结果!

看到了吧!async/await 是根源所在,对吧?但要记住,它虽然重要,但并非在所有地方都有必要使用!我们可以进行优化…

让你的代码运行得更快:ValueTask 现在,让我们谈谈 ValueTask。可以把它看作是在特定情况下比 Task 更高效的版本。以下是你可能想要使用它的情况:

是的,

代码语言:javascript代码运行次数:0运行复制
// 之前:使用常规的 Task
public async Task<int> GetDataAsync(string key)
{
    var value = await _database.GetValueAsync(key);
    return value;
}

但是,

代码语言:javascript代码运行次数:0运行复制
// 之后:高效地使用 ValueTask
publicValueTask<int>GetDataAsync(string key)
{
    // 如果数据在缓存中,立即返回
    if(_cache.TryGetValue(key,outvarvalue))
    {
        returnnewValueTask<int>(value);
    }
    
    // 如果不在缓存中,回退到异步操作
    returnnewValueTask<int>(_database.GetValueAsync(key));
}

你应该在什么时候使用 ValueTask 呢?

  • 当你的方法经常无需等待就能立即返回时
  • 当你处理大量小型、快速的操作时
  • 当你构建高性能系统时