消息循环是存在于任何本机 Windows 程序中的一小段代码。它大致看起来像这样:
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
GetMessage() Win32 API 从 Windows 检索消息。您的程序通常将 99.9% 的时间花在那里,等待 Windows 告诉它发生了一些有趣的事情。TranslateMessage() 是一个翻译键盘消息的辅助函数。DispatchMessage() 确保窗口过程与消息一起被调用。
每个启用 GUI 的 .NET 程序都有一个消息循环,它由 Application.Run() 启动。
消息循环与 Office 的相关性与 COM 有关。Office 程序是支持 COM 的程序,这就是 Microsoft.Office.Interop 类的工作方式。COM 代表 COM coclass 处理线程,它确保在 COM 接口上进行的调用始终来自正确的线程。大多数 COM 类在注册表中都有一个注册表项,用于声明它们的 ThreadingModel,到目前为止,最常见的类(包括 Office)使用“Apartment”。这意味着调用接口方法的唯一安全方法是从创建类对象的同一线程进行调用。或者换一种说法:到目前为止,大多数 COM 类都不是线程安全的。
每个启用 COM 的线程都属于一个 COM 单元。有两种,单线程单元(STA)和多线程单元(MTA)。必须在 STA 线程上创建单元线程 COM 类。您可以在 .NET 程序中看到这一点,Windows 窗体或 WPF 程序的 UI 线程的入口点具有 [STAThread] 属性。其他线程的单元模型由 Thread.SetApartmentState() 方法设置。
如果 UI 线程不是 STA,Windows 管道的大部分将无法正常工作。值得注意的是拖放、剪贴板、Windows 对话框(如 OpenFileDialog)、控件(如 WebBrowser)、UI 自动化应用程序(如屏幕阅读器)。还有很多 COM 服务器,比如 Office。
STA 线程的硬性要求是它不应该阻塞并且必须泵送消息循环。消息循环很重要,因为 COM 使用它来将接口方法调用从一个线程编组到另一个线程。尽管 .NET 使封送调用变得容易(例如 Control.BeginInvoke 或 Dispatcher.BeginInvoke),但实际上这是一件非常棘手的事情。执行调用的线程必须处于众所周知的状态。你不能随意中断一个线程并强制它进行方法调用,这会导致可怕的重入问题。线程应该是“空闲的”,而不是忙于执行任何改变程序状态的代码。
也许您可以看到这会导致什么:是的,当程序正在执行消息循环时,它是空闲的。实际的编组是通过 COM 创建的隐藏窗口进行的,它使用 PostMessage 让该窗口的窗口过程执行代码。在 STA 线程上。消息循环确保此代码运行。