消息循环是存在于任何本地Windows程序中的一小段代码。它大致看起来像这样:
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
GetMessage()是Win32 API中的一个函数,它从Windows系统获取消息。大多数情况下,程序会在此处等待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 Forms或WPF程序的UI线程入口点具有[STAThread]属性。其他线程的公寓模型由Thread.SetApartmentState()方法设置。
如果UI线程不是STA,Windows管道的大部分功能将无法正常工作。特别是拖放、剪贴板、像OpenFileDialog这样的Windows对话框、像WebBrowser这样的控件、屏幕阅读器等UI自动化应用程序以及许多COM服务器,如Office。
STA线程的严格要求是它不能阻塞并且必须抽象一个消息循环。消息循环很重要,因为这是COM用于从一个线程传递接口方法调用到另一个线程的方式。尽管.NET使调用易于处理(例如Control.BeginInvoke或Dispatcher.BeginInvoke),但实际上这是一件非常棘手的事情。执行调用的线程必须处于已知状态。您不能随意中断线程并强制它进行方法调用,否则会引起可怕的重入问题。线程应该是“空闲的”,没有忙于执行任何正在改变程序状态的代码。
也许您可以看到它的含义:是的,当程序执行消息循环时,它是空闲的。实际的传递通过COM创建的隐藏窗口进行,它使用PostMessage让该窗口过程在STA线程上执行代码。消息循环确保此代码运行。