线程模式是微软的COM基础中的极其重要的概念.一定要吃透!
初始一个STA套间实际上是相当于开了一个消息窗口,所有调用经此窗口过程调度到组件内.
[STAThread]
可以理解成CoInitialize(NULL);
初始一个STA套间实际上是相当于开了一个消息窗口,所有调用经此窗口过程调度到组件内.
[MTAThread]
可以理解成CoInitializeEx(NULL,COINIT_MULTITHREADED )
这经常是一个对初入com大门的人来说,有一定难度但必须过的一道关.
线程模型的存在就是线程规则的不同导致的,而所谓的线程规则就只有两个:代码是线程安全的或不安全的,即代码访问公共数据时会或不会发生冲突。
由于线程模型只是个模型,概念上的,因此可以违背它,不过就不能获得COM提供的自动同步(序列化)调用的好处了。
1.线程模型是干嘛用的?
解决”多个线程”“同时”调用你的COM组件的并发控制。客户没有你的COM的源代码,它不知道你的组件是怎么写的,是不是线程安全的(是否用CriticalSection或Mutex保护了临界资源),所以要有一种机制来声明组件的线程安全性,你开发时指定了组件的线程模型,客户端一看,哦,它就知道该怎么写调用的代码。
2.啥时候用操心线程模型?
全看客户端是单线程还是多线程,单线程不用操心,怎么调都没事,多线程就来事了,跨线程调用时就要考虑。
3.线程模型的一堆概念都是哪跟哪啊?
首先一分为二:客户端和组件。客户端用CoInitializeEx进入一种套间,Apartment、Free是指的客户端进入的套间种类;组件要向注册表写入自己兼容什么样的客户端套间,是Apartment,还是Free,还是两种都兼容(Both)。
4.客户端的线程套间和组件的不一致了咋办,难道我调个COM组件还得查注册表看看兼容什么线程模型?
不一致时以组件为主。客户端建的是Apartment,组件兼容Free,那COM背地里会在客户端建一个Free套间,把组件放进去。反之,会建一个Apartment套间把组件放进去。总之以组件为主,这是关键,只有这样,你才不用关心组件的线程安全性,COM服务替你在后台办妥了。
5.听说过列集这个名词,是什么啊,啥时候用?
记死了,跨线程调用组件就得列集,没的商量。传出接口的线程列集,使用接口的线程散列。列集说白了就是不让你直接调用组件的接口,而是调用接口的代理。COM服务在中间插一杠子,干啥,实现COM线程安全那一揽子事呗,它不拦截你的调用它怎么实现啊,所以就给你个代理,所以就列集了呗。
内容来源:https://www.cxybb.com/article/crybird/80808960
关于多线程问题方面,COM库做出了如下规则(不是COM标准,是COM库为了简化多线程编程中对组件的调用而制定的):
1. COM库提供两种套间,单线程套间和多线程套间,COM组件的编写者最好提供对应的属性(后面会提到),COM组件的使用者要在套间里创建和调用组件。
2. COM库对所有的调用进行参数调整(如果需要),不管是对进程内服务器的调用,还是对进程外服务器的调用。
3. 线程内调用、跨线程调用、跨进程调用都用统一的方式。需要用代理的会用代理。
个人理解COM套间线程模型就是一组访问控制规则,规范了线程如何访问套件对象。STA表示被标注STA的线程只能单独访问套间对象。MTA 表示被标注为MTA线程可以和其他MTA线程一起访问套间对象。
Win32中的线程,典型的Win32程序具有两种不同类型的线程:用户界面线程和工作者线程。用户界面线程是同一个或者多个窗口关联着的,这些线程具有自己的消息循环,以便能对用户输入做出反应。工作者线程用于后台处理,它们没有任何窗口与之相关联,通常也没有消息循环。
COM中使用的线程类型与Win32的两种类型的线程是相同的,只不过换了不同的名称而已。在COM中与Win32中的用户界面线程相对应是“套间线程”,而与工作者线程相对应的是自由线程。
既然COM线程与Win32线程并没有什么差别,那么为什么COM还需要定义自己的线程呢?其原因在于参数调整与线程同步。
5.1 套间线程: 当线程调用CoInitializeEx时,使用参数 COINIT_APARTMENTTHREADED 时,此时这个线程就被称为是套间线程。
对于套间线程我们可以将其想象成一个用户界面线程。
① 我们知道一个用户界面线程将拥有所有在该线程中创建的窗口,同理一个套间线程将拥有所有它所创建的组件;
②
一个套间中组件只能由相应的套间线程来调用。如果某个线程给另外一个线程所拥有的窗口发送一条消息(SendMessage函数可以向不同线程中创建的窗口发送消息),Windows将把此消息放到那个窗口所在的线程消息队列中。窗口的消息循环将在创建此窗口的线程中执行。当消息循环取出一条消息并调用窗口过程时,此窗口过程也在,创建窗口的线程上运行。对于套间中的组件亦是如此,假设另一个线程调用一个套间中的某个组件的方法,COM库将把此请求放到套间的队列中。消息循环将取出此调用请求并在套间线程上执行相应的方法。通过这种消息调用机制,保证了套间中的组件只在相应的套间线程中被调用,也就是说不会出现多个线程并发访问这个组件的情况,因此组件就不需要考虑线程同步的问题了。
也就是说如果在某个时刻,一个线程是套间线程,则COM库将完成对组件的同步调用,实现方法:使用窗口的消息循环机制,将不同线程中的函数调用,封装成消息发送到套间线程,然后再套间线程中执行代码,从而保证,组件不会被并发访问。
5.2 自由线程:当线程调用CoInitializeEx时,使用参数 COINIT_MULTITHREADED 时,此时这个线程就被称为是自由线程。
5.3 一个线程不会固定的只是 套间线程或是 自由线程,因为一个线程可以多次进入不同的套间,当该线程进入的是单线程套间是此时这个线程就是套间线程,当该线程进入多线程套间时,这个线程就是自由线程。
COM将在套间线程上同步对组件的调用。对于由自由线程创建的组件的调用,COM不能使之同步。若某个组件是由自由线程创建的,则任意线程均可在任意时候调用它。此时组件开发者应保证对组件访问的同步。也就是说,此时组件应是线程安全的。实际上使用自由线程,同步的工作将由 COM移至组件本身。
此时由于COM并不同步对组件的调用,因此自由线程不需要消息循环。由自由线程创建的组件将是一个可以供任意线程访问的组件。此时创建此组件的线程并不拥有此组件,此组件将被所有的线程共享,并可供所的线程自由访问。
5.4 单线程套间(single-threaded apartments)和多线程套间(multithreaded apartments)
前面我们描述的套间线程和自由线程,我们COM线程的角度来讨论的,现在我们从COM套间的角度来看看COM线程,其实如果一个线程某个时刻进入了一个COM单线程套间我们就称此线程此时是一个套间线程,如果一个线程在某个时刻进入了一个COM多线程套间我们就称此线程此时是一个自由线程。