好吧,让我们摆出事实。
MessageBox.Show
创建自己的消息泵。这使用 current ThreadContext
,我假设它与您的情况下的 UI 线程相同 - 换句话说,您的应用程序被冻结。Show
是模态的,尽管名称可能暗示。
MessageBox
不是- 它是由它创建的,这也是它的消息泵所在的地方。Form
user32.dll
- 您创建任务的方法最终会导致将任务推送到
ThreadPool.QueueUserWorkItem
. 队列似乎是按线程维护的(它是线程静态的)。当您询问任务何时实际执行时,事情会变得复杂,因为现在我们正在处理来自 .NET 外部的异步回调。编辑:我错了。事实上,当前的同步上下文是一个派生类,WindowsFormsSynchronizationContext
它实际上确实将工作项放入了与 Windows 消息传递相关联的调用队列中。
- 一切都发生在一个线程上,就这么简单。
- 该任务在调试输出之后执行。它与任务内部无关
MessageBox.Show
。
如果我await
在调试输出 ( await Task.Delay(1000);
) 之后添加另一个,会发生一件有趣的事情 - “任务!” 显示,然后是“退出!” 一秒钟后。同时两个消息框?!这是什么巫术造成的?!
很明显,“退出!” 是“任务!”的模态。形式,而不是我们的父母。换句话说,第二个消息框不知何故必须在第一个消息框的“上下文”中运行。
这与我在原始答案中所说的有关。模态框窃取它正在运行的线程,并处理消息泵送。当第二个await
执行时,它在我们的“任务!”上运行。表单,而不是(被阻止的)父表单。
如果我们使用Thread.Sleep(1000);
而不是 ,则await
此行为将丢失。但是,Thread.Sleep
确实在“任务!”之前运行。消息框,事实证明,只要我们关闭“任务!” 表格,“退出!” 立即出现,而不是等待一秒钟,而“任务!” 有延迟。
这些表单依赖于 Windows 消息传递。模态表单“窃取”其所有者的句柄,并处理这些消息。只有在消息框关闭后,才会向父级发送 WM(简单的“设置焦点”消息)。
但是,await
在我们的场景中,在 UI 线程上工作,但在消息循环之外。因此,当我们在显示第一个对话框后等待时,等待之后的代码就像在第一个对话框中运行一样执行 - MessageBox 的所有者在创建底层本机消息框之前确定(它不是 .NET 表单!) ,所以它获取当前活动窗口 - 在我们等待的情况下,这就是“任务!” 形式。谜团已揭开。
剩下的谜团是为什么任务在MessageBox.Show("Exit!");
调用和消息框之间的某个地方运行,实际上窃取了消息循环。
这将我们带到了大结局:
我们创建了我们的小任务。但是,它有一个 windows 窗体同步上下文,所以它不做任何事情,只是将任务添加到窗体上的队列中。这是在队列的顶部,所以一旦我们解除对 UI 线程的控制,它就会被执行。
如果我们在显示“退出!”之前等待。对话框,一切都清楚了——“任务!” 首先显示,并且在某些时候(因为它没有通过消息传递队列),“退出!” 显示为它的孩子。
如果我们不等待,MessageBox.Show("Exit!");
将进入一个模态消息循环(我们可以通过事件得知Application.EnterThreadModal
)。然后,调用 WinAPI (user32.dll)MessageBox
方法,该方法立即抽水。这会读取与我们的排队Invoke
调用相关的排队 WM——“Task!”的任务。它立即被调用,并有效地阻止了原始 Message.Show 调用,因为它无法处理自己的消息。
总而言之,另一个不让 UI 线程复杂化的好理由。看起来,尤其是不同的MessageBox.Show
,因为它的作用远不止眼前所见。
实际上,您将在 UI 线程之外运行您的任务,并且只有需要访问 UI 的延续才会在 UI 线程中。尽管如此,如何MessageBox
劫持发生的事情还是非常有趣的——如果你的后台任务卡在 UI 线程上调用某些东西,这可能会适得其反,而这实际上是由消息框接管的;你的异步性出现了:))