在c#异步方法中,值类型局部变量通常保存在栈上,但在编译器生成的状态机结构体中,它们会被提升为字段并存储在堆上的状态机对象实例中。这是因为异步方法的执行可能被挂起和恢复,需要持久化保存所有局部变量的状态。

异步方法编译后的状态机结构是怎样的
当C#编译器遇到`async`关键字时,会将方法重写为一个实现了`IAsyncStateMachine`接口的结构体。该结构体包含一个`CurrentState`字段用于跟踪执行位置,以及所有被`await`跨越的局部变量作为字段。这些字段在堆上分配的状态机对象中被存储。

using System;
using System.Runtime.CompilerServices;
public class Program
{
public static async Task Main()
{
int localValue = 42;
await Delay();
Console.WriteLine(localValue);
}
private static async Task Delay()
{
await Task.Delay(100);
}
}
// 编译器生成的简化状态机示意:
// struct <Main>d__0 : IAsyncStateMachine
// {
// public int CurrentState;
// public int localValue; // 原局部变量成为字段
// public AsyncMethodBuilder<Task> builder;
// // ... 其他成员
// }上述代码展示了简单的异步方法。编译器会将`localValue`从栈上的局部变量提升为状态机结构体的字段。当调用`Delay()`时,如果任务未完成,当前执行点挂起,`localValue`的值保存在堆上的状态机对象中,以便后续恢复时能继续使用。
哪些局部变量会被提升到状态机中
只有那些生命周期跨越`await`表达式的局部变量才会被提升到状态机结构体中。如果在`await`之前声明且仅在`await`之前使用的局部变量,可能仍保留在栈上或通过优化处理。但为了安全起见,编译器通常会将所有在异步方法中声明的局部变量都纳入状态机,除非经过严格的逃逸分析证明不需要持久化。

using System;
using System.Threading.Tasks;
public class Example
{
public static async Task Test()
{
int beforeAwait = 10; // 仅在使用前存在,可能不提升
int afterAwait = 20; // 在await后使用,必须提升
await Task.Delay(100);
Console.WriteLine(beforeAwait); // 使用前需确保已提升
Console.WriteLine(afterAwait); // 必须从状态机读取
}
}在这个例子中,`beforeAwait`和`afterAwait`都可能被提升为状态机的字段,因为编译器难以精确优化它们的生存期。实际行为取决于编译器的优化策略,但通常所有局部变量都会被纳入状态机以确保正确性。这会导致额外的内存分配在堆上,而非栈上。
状态机中的值类型对性能有何影响
将值类型局部变量提升为状态机字段意味着这些值会在堆上分配,增加了垃圾回收的压力。对于大型结构体或频繁调用的异步方法,这种提升可能导致明显的性能开销。开发者应尽量减少异步方法中局部变量的数量,避免使用大型结构体,以降低堆分配频率。
,


















