STAThreadAttribute是c# windows程序由template带来的一个默认属性。VB.net没有这个属性,因为vb.net的编译器会自己加上这个标记。
[STAThread]
static Main()
{...}
STA, Single Threaded Apartment。知道缩写的全称,还远远不够。
一、Threaded Apartment
这个概念是从COM来的。在16位操作系统时代,组件线程模型(Component Thread Model,用来处理组件对象在多线程的环境下交
互、访问的方式)还是单线程模式,即进程里只有一个线程在运行,所有的工作都由一个线程完成。效率可想而知。
Threaded Apartment不是一个具体的对象,它是一个概念。它描述了一个Apartment内的对象如何交互,不同Apartment内的对象
如何交互;Apartment内对象和线程的关系等等。
1.Single Threaded Apartment
Single Threaded Apartment并不是说一个程序只能有一个线程,它是说:
一个Apartment内只能有一个线程;一个线程也只能属于一个Apartment;
一个Object只能属于一个Apartment,但一个Apartment可以包含多个Object;
一个进程内可以有多个Single Threaded Apartment(多线程工作)。
同一个Apartment内的对象可以通过接口引用直接访问;不同Apartment之间的对象则要通过创建Proxy/stub来进行间接交互。
Single Threaded Apartment的实现主要依靠了message loop。COM会为Single Threaded Model创建一个Message Loop,所有对
Apartment内对象的访问,不管来自多少个Thread,都要通过发送消息(DispatchMessage)给message loop的队列,序列化,然后
由COM转发给对象。这样做的好处就是避免了对象由于并行访问带来的问题。于是运行于Single Threaded Apartment Model的对
象可以不用考虑并行的实现:COM可以通过message loop完成对象访问的并行控制。
UI相关的COM对象一般都要求运行在Single Threaded Apartment模型下。
2.Multiple Threaded Apartment
对应的Multiple Threaded Apartment则不用实现message loop。因为在Multiple Threaded Apartment内可以有多个线程,他们
可以随意访问Apartment内的对象。因此运行在MTA模型内的对象必须自己实现并行控制。
可见,STA和MTA都可以认为是线程和对象的容器。只是两者对于多线程访问对象进行的并行处理是不同的。
二、Threaded Apartment的实现
HRESULT CoInitializeEx(void * pvReserved, DWORD dwCoInit)
第一个参数默认为NULL;第二个参数标志是single threaded还是multiple threaded。
当某个线程要初始化COM Library的时候,就要通过这个方法设置并行访问模式,然后为这个线程创建一个Apartment。
线程通过调用方法CoInitializeEx(null, COINIT_SINGLETHREADED),把自己加入到一个STA模型中。
线程通过调用方法CoInitializeEx(null, COINIT_MULTITHREADED),把自己加入到一个MTA模型中。
线程要使用COM Library,那么至少要调用CoInitializeEx方法一次,也只能调用这个方法一个。多次调用也可以,但是只能重复设置一种并行访问模式,不能改变。
三、STAThread属性
如果你的程序只执行纯c#代码,不调用非托管代码,那么这个属性没什么意义。
STAThread属性只有在你用COM Library进行COM InterOP的时候,或者说第一次调用非托管代码时才会发挥作用。
注:除了为了进行COMInterOP,设置STAThread属性还是为了让程序可以host ActiveX控件。
运行时在需要用到COM Library的时候,调用CoInitializeEx方法,并且根据STAThread属性标记决定方法的第二个参数是single threaded还是multi threaded,即为线程创建STA还是MTA。
注:STAThread属性其实会让线程调用OleInitialize()方法而不是CoInitializeEx(),OleInitialize()方法可以为支持drag-and-drop进行自动的初始化。
例如,你使用SaveFileDialog或者Clipboard的时候,就需要和widnows component交互,你必须为线程入口方法(Main)添加STAThread
属性标记。
由于默认.net的应用程序是multiple threaded,因此如果你不设置STAThread属性,运行时就会在需要的时候创建一个Multi
Threaded partment。
但是windows component好像一般都是要运行在single threaded apartment内的,所以它不能在MTA模型下正常工作,因此,这
时你使用SaveFileDialog或者Clipboard,运行时就会发生异常:You must set the [STAThread] attribute.....
[注意]
1.如果不设置STAThread或者MTAThread属性,那么主线程的ApartmentState属性值是MTAThread。
2.不管是在调用非托管代码之前或者之后,如果在Main方法设置了STAThread属性或者MTAThread属性,线程不可以通过SetApartmentState()方法改变线程的ApartmentState属性,如STA改为MTA,或者相反。
3.线程始终可以调用SetApartmentState方法,只要ApartmentState参数和已经标记的属性是一致的。
4.设置线程的ApartmentState属性为UnKnown?
ApartmentState.Unknown是个狠奇怪的属性。即使你给Main方法设置了MTAThread属性或者不设置任何线程模型(不设置,则默认为MTA),你仍然可以用SetApartmentState(ApartmentState.Unknown)把ApartmentState设置为Unknown,意思是还没有设置ApartmentState。这时候你去看主线程的ApartmentState:
ApartmentState在进行COM InterOP之前是MTA,在进行了一次COM InterOP之后就变成了STA。
看来还是CLR帮我们做了一些事情。
另外,一旦访问了非托管代码,创建了确定的线程模型,就不能再改变ApartmentState了,Unknown也不行。
[问题]
1.为什么不管是STA还是MTA,Textbox总是可以使用。但是SaveFileDialog只能在STA下使用。Textbox控件和FileDialog这种控件什么区别?Textbox不属于widnows component么?
2.按照Threaded Apartment模型介绍,即使是线程运行在MTA模式下,它还是可以访问STA模式内的对象的。为什么必须要设置这
个属性?
四、关于Cross Thread Access Control
我们写程序时,一个新线程访问主线程的Control会产生Exception: Cross-thread operation not valid。
如果我们要访问或者改变这个控件,那么我们只能
调度(Marshal)创建它的线程,也就是主线程去做这个事情(使用Invoke()),
这个过程就叫做Marshalling。
这个过程和STAThread属性或者MTAThread属性应该没有关系,即使你设置MTAThread属性,你还是无法在新线程访问主线程创建的
Control。当然.net的这种做法,应该还是借鉴了COM UI Component的Single Threaded Apartment模式特性,所有其他线程对
COM UI Component的访问,必须被Marshal为component对象所在的Apartment线程调用。