采用VSTO或者Shared Add-in等技术开发Excel插件,其实是在与Excel提供的API在打交道,Excel本身的组件大多数都是COM组件,也就是说通过Excel PIA来与COM进行交互。这其中会存在一些问题,这些问题如果处理不好,通常会导致在运行的时候会抛出难以调试的COM异常,从而导致我们开发出的Excel插件的不稳定。
和普通的WinForm程序一样,Excel也是一种STA(Single Thread Apartment)线程的应用程序,Excel插件是寄宿在Excel中运行的,这也就意味着插件也是一种STA线程的应用程序。插件在操作Excel的时候,如果是在Excel的主线程中,可以直接获取Excel对象进行操作,比如写入单元格值,对单元格进行格式化等操作。但是通常,我们会在多线程或者后台工作线程中去处理一系列复杂的数据或者逻辑,待处理完成获得结果之后,再像WinForm那样,回到UI线程中,去更新界面信息,对于Excel插件来说,就是回到Excel的主线程上来,然后再更新界面。但是Excel又是一种不同于一般Winform 类型的STA,它是COM并且Excel插件是寄宿在其上的,所以还有一些需要注意的地方。
本文首先介绍什么是STA应用程序及其工作原理,然后介绍一般的Winform程序的界面刷新逻辑,以及在这其中非常重要的一个名为SynchronizationContext对象,最后介绍在Excel插件中如何获取Excel主线程,以及这其中需要注意的地方。
Excel插件的最难处理的地方在于其应用程序的稳定性,了解了Excel中的线程以及其机制对增强系统的稳定性会有很大的帮助。
1. STA(Single Thread Apartment)
COM组件的线程模型被称之为Apartment模型,即COM对象初始化时其执行上下文(Execution Context),他要么和单个线程关联STA(Single Thread Apartment ) 要么和多个线程关联MTA(Multi Thread Apartment)。
通常COM对象为了保护其自身维护的数据不被破坏,需要运行时来保证其不被多个线程同时调用;另外也需要运行时来保证对COM对象的调用不会阻塞UI线程。Apartment 就是COM对象生存的地方,一个Apartment可以包含一个或者多个线程。对一个COM对象的调用可以由该COM生存的Apartment中的任何一个线程接受和处理。如果一个Apartment中只有一个线程,那么就是STA线程,否则就是MTA,这个是在程序初始化COM组件的时候即确定下来的。一个进程可以包含多个STA,但是只有一个MTA。
STA模型是COM对象使用的一种非线程安全的模型,这意味着他不能处理自己的线程同步,通常在UI组件中使用这种模型。因此,如果其他线程需要和UI对象进行交互,需要将消息封送(marshall)到STA线程中。在Windows 窗体应用程序中,这一过程是通过窗口消息队列 (message pumping system)来实现的。当客户线程以STA 模式启动时,系统将为STA创建一个隐藏窗口类,所有的对COM对象的调用都会放到这个隐藏窗口的消息队列中。
如果COM对象能够处理其本身的同步逻辑,那么就是MTA模型了,他似的多个线程能够同时和对象进行交互,而不需要进行消息调用的封送。
COM组件在创建的时候采用哪种模型,可以在注册表项的ThreadingModel值中指定:
COM组件在注册表项中的ThreadingModel属性中会有一下四个属性:
Main thread. COM对象创建 在宿主程序的主UI线程上,所有的调用必须封送到 主UI线程上 .
Apartment. 表示该COM对象能够运行在任何但单线程模型的线程上,如果该线程是STA线程创建的,则对象运行在该STA线程上,否则该对象运行在主STA线程上,如果主STA线程不存在,系统则会自动创建一个。
Free. 表示该COM对象运行在MTA上。
Both. 表示该COM对象在那个模型上取决于创建Apartment的类型。