这段代码做了您所期望的事情:快速连续两次单击按钮将启动两个任务,并打印两条具有两个不同局部变量值的消息。
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
型
问题是:方法的第一次post-await恢复如何为本地值维护一个单独的堆栈,并与第二次post-await恢复保持分离**?
我知道(或者至少有一个模糊的概念)线程有独立的堆栈,但在这种情况下,所有内容都在单个GUI线程上(通过打印ManagedThreadId
进行验证)。
这个问题类似于C# async: How does a thread remember its local variables?,但我不知道我是否会认为它们是真正的重复,我的问题是关于解开时间上重叠的调用。
3条答案
按热度按时间db2dz4w81#
在内部,
async
调用通过创建一个状态机来工作。各种状态机(每个调用一个)独立运行,并具有各自的独立变量。看看你的例子是什么降低到例如在“这里”
gev0vcfq2#
c#编译器为每个
async
方法创建一个实现状态机的内部类。方法中的局部变量被转换为类的字段。每次调用该方法都会创建该内部类的一个新示例。这就是方法的重叠执行如何使用不同的局部变量集:局部变量由内部生成的StateMachine类的不同示例的字段表示。
您可以在https://sharplab.io/#gist:b0bda78721855033b6423b6cd872d05e上看到编译器生成的代码示例
wgmfuz8q3#
我知道(或者至少有一个模糊的概念)线程有独立的堆栈,但在这种情况下,所有内容都在单个GUI线程上(通过打印
ManagedThreadId
进行验证)。这是真的,但
button1_Click
事件处理程序实际上是一个超越线程的异步执行流。如果执行流是同步的,例如将await Task.Delay(2000)
替换为Thread.Sleep(2000)
,您甚至无法单击按钮两次,因为第一次单击后UI将冻结。在后台,
Task.Delay
任务在ThreadPool
线程上完成。此时,await
之后的继续通过透明但复杂的机制重新调度到UI线程,该机制涉及WindowsFormsSynchronizationContext
类和Control.BeginInvoke
方法(源代码)。您可以通过使用ConfigureAwait
方法配置await
来防止此重定向:字符串
这样,
await
之后的continuation将直接在完成Task.Delay
任务的线程上运行,这将是一个ThreadPool
线程,因为Task.Delay
方法是如何在内部实现的。更多细节请参见此答案。