异步按钮点击处理程序为各调用维护独立局部变量的机制是什么?
Async方法局部变量独立保留的实现机制
测试代码
private bool _dummy; private async void button1_Click(object sender, EventArgs e) { int a; if (_dummy == false) { a = 1; _dummy = true; } else { a = 99; _dummy = false; } Debug.WriteLine($"{DateTime.Now:HH:mm:ss:fff} {Thread.CurrentThread.ManagedThreadId} {a} click pre delay"); Task dtask = Task.Delay(2000); await dtask; Debug.WriteLine($"{DateTime.Now:HH:mm:ss:fff} {Thread.CurrentThread.ManagedThreadId} {a} {dtask.Id} click post delay"); }
输出结果
15:21:52:111 1 1 click pre delay 15:21:52:595 1 99 click pre delay 15:21:54:120 1 1 7 click post delay 15:21:54:615 1 99 9 click post delay
问题
快速点击按钮两次后,两次调用的局部变量a在await恢复后仍能保留各自的值(1和99),且所有操作都在同一个GUI线程执行。是什么机制实现了这一效果?第一个await后的方法恢复如何保留局部变量的独立栈,且与第二个await后的恢复互不干扰?
回答
这个效果的核心是C#编译器对async方法的状态机转换机制:
- 当方法被标记为
async时,编译器会自动将其转换为一个实现了IAsyncStateMachine接口的私有类。 - 原方法中的局部变量(比如
a、dtask)以及方法的执行状态(比如当前走到await的哪个阶段),都会被转换成这个状态机类的实例字段。 - 每次调用该async方法时,都会创建一个独立的状态机实例。第一次点击按钮生成一个实例,第二次点击生成另一个完全独立的实例,两个实例的字段各自存储对应调用的局部变量值,互相没有关联。
当执行到await dtask时,方法暂停并返回,GUI线程可以继续处理其他消息(比如第二次按钮点击)。当Task.Delay完成后,对应的状态机实例会被调度回GUI线程继续执行剩余代码,此时它会读取自身实例里存储的a和dtask的值,所以两次恢复操作各自操作的是不同实例的数据,自然不会互相干扰。
简单来说,这里不是依靠线程栈保存局部变量,而是把局部变量转化为状态机实例的字段,每个方法调用对应一个独立实例,就算在同一个线程恢复,操作的也是不同的实例数据。
内容的提问来源于stack exchange,提问作者amonroejj
相关产品推荐
相关产品推荐

