揭开.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;
}
为什么这很重要(对性能的影响) 这种转换会带来一些代价:
- 内存使用:每个异步方法都需要额外的内存来:
- 存储状态机
- 跟踪局部变量
- 创建
Task
对象
- 速度:会有一些额外的工作要做:
- 设置状态机
- 在代码的不同部分之间切换
- 管理所有这些部分
看看这个…
代码语言: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
呢?
- 当你的方法经常无需等待就能立即返回时
- 当你处理大量小型、快速的操作时
- 当你构建高性能系统时
发布评论