ref:http://blog.csdn.net/dyllove98/article/details/9735955

1 COM中的公寓

本文讨论进程内COM组件。以一个示例直观演示STAThread和MTAThread的作用和区别。

1.1 基本规则

公寓是COM组件的运行环境,日常生活中公寓是用来住人的,COM中的公寓是用来住COM组件的对象的,每个COM对象必须且只能位于一个公寓中:单线程公寓(STA)或多线程公寓(MTA)。

每个进程可以有0或多个STA。

每个进程可以有0或1个MTA。

一个线程只能关联到一个公寓。因此所有关联到MTA的线程都是关联到进程唯一的一个MTA。

本线程访问与本线程关联的STA中的COM对象不需要列集,直接访问。

其他线程对STA中的COM对象的访问需要列集(marshal),通过列集,自动实现了多线程访问下的同步

所有线程对MTA中的COM对象的访问不需要列集,直接访问,需要COM组件自身实现多线程下的同步

(列集就是将函数调用序列化,实现跨边界调用,在Windows中通常是通过消息机制实现。在COM中RPC就是列集,在WinForm中Control.Invoke就是一种列集,Remoting也是列集,WCF也是列集,最近流行的RESTfull也是。。。)

1.2 公寓类型匹配

一个COM对象所属的公寓,由两个地方的配置确定:组件公寓模型客户端线程公寓模型

  1. 组件公寓模型是在组件注册到注册表时设定,通过组件公寓模型,组件声明自己可以住在什么样的公寓里。可选项包括:Apartment,Free和Both。Apartment,我只能住在单线程公寓中;Free,我只能住在多线程公寓中;Both,我随意,单线程公寓或多线程公寓都可以。
  2. 客户端线程公寓模型就是线程的公寓模型,表示当前线程提供什么样的公寓。可选项包括:单线程公寓(STA)或多线程公寓(MTA),也就是本文所讨论的STAThread和MTAThread。

下表列出了组件对象最终会住在什么公寓中的组合表:

客户端线程公寓模型 \ 组件公寓模型  Apartment Free Both
STA STA MTA STA
MTA STA MTA MTA

如果组件公寓模型为Apartment,不管客户端线程公寓模型是什么,组件最后都住在STA中,因为组件说了“我只能住在单线程公寓中”。如果当前线程是MTA,COM库会后台创建一个STA来放该组件的对象。

如果组件公寓模型为Free,不管客户端线程公寓模型是什么,组件最后都住在MTA中,因为组件说了“我只能住在多线程公寓中”。如果当前线程是STA,COM库会检查当前进程的MTA有没有创建,没有就创建进程的MTA,然后将组件的对象放在MTA中。

如果组件公寓模型为Both,组件最后都住在与当前线程关联的公寓中,如果当前线程是STA,它就住在STA中;当前线程是MTA,它就住在MTA中。本文中,我们会创建一个并注册一个Both类型的组件,然后分别在STA和MTA中创建该组件的对象。

1.3 .NET中设置客户端线程公寓模型

在.NET中使用COM组件时,需要设置线程的公寓模型。

在.NET中可以通过STAThread和MTAThread属性来设置主线程的公寓类型, 通过Thread.SetApartmentState可以设置工作线程的公寓类型。

对于WinForm或WPF应用程序,主线程的公寓模型必须为STA,因为用户界面对象都不是线程安全的。

对于控制台应用程序,主线程的公寓模型可以随意设置,为了方便,我们用控制台应用程序来演示。(用WinForm也完全可以演示,只是需要在工作线程中创建组件的对象。)

2 一个简单的COM组件

为了演示单线程公寓和多线程公寓的区别,我们用ATL实现定义一个简单的COM组件SimpleCom,该组件包含一个返回字符串的方法Hello,返回的字符串分三步合成,每步之间通过Consume方法来消耗较长CPU周期,确保Hello不会在操作系统的一个时间片内被执行完成,保证Hello函数被并发执行,以达到演示的效果。代码如下:

 1 // CSimpleCom.h
2 class ATL_NO_VTABLE CSimpleCom :
3 public CComObjectRootEx<CComSingleThreadModel>,
4 public CComCoClass<CSimpleCom, &CLSID_SimpleCom>,
5 public IConnectionPointContainerImpl<CSimpleCom>,
6 public CProxy_ISimpleComEvents<CSimpleCom>,
7 public IDispatchImpl<ISimpleCom, &IID_ISimpleCom, &LIBID_ATLTestLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
8 {
9 public:
10 CSimpleCom()
11 {
12 this->m_iMember = 0;
13 }
14
15 DECLARE_REGISTRY_RESOURCEID(IDR_SIMPLECOM)
16
17
18 BEGIN_COM_MAP(CSimpleCom)
19 COM_INTERFACE_ENTRY(ISimpleCom)
20 COM_INTERFACE_ENTRY(IDispatch)
21 COM_INTERFACE_ENTRY(IConnectionPointContainer)
22 END_COM_MAP()
23
24 BEGIN_CONNECTION_POINT_MAP(CSimpleCom)
25 CONNECTION_POINT_ENTRY(__uuidof(_ISimpleComEvents))
26 END_CONNECTION_POINT_MAP()
27
28
29 DECLARE_PROTECT_FINAL_CONSTRUCT()
30
31 HRESULT FinalConstruct()
32 {
33 return S_OK;
34 }
35
36 void FinalRelease()
37 {
38 }
39
40 public:
41 STDMETHOD(Hello)(BSTR* a);
42 private:
43 int m_iMember;
44 CString m_str;
45 };
46
47 OBJECT_ENTRY_AUTO(__uuidof(SimpleCom), CSimpleCom)
 1 // CSimpleCom.cpp
2 double Cosume()
3 {
4 double d = 0;
5 for (int i = 0; i < 1000*1000*300; i++)
6 {
7 d += i;
8 }
9 return d;
10 }
11
12 STDMETHODIMP CSimpleCom::Hello(BSTR* a)
13 {
14 m_str = L"0>你好! ";
15 Cosume();
16 CString str;
17 str.Format(L"1>m_iMember = %d; " , this->m_iMember++);
18 m_str += str;
19 Cosume();
20 m_str += L"2>再见~";
21 *a = m_str.AllocSysString();
22 return S_OK;
23 }

将组件的ThreadingModel设置为Both,生成项目,组件会自动注册。

接下来创建C#客户端,使用该组件。

3 C#客户端

新建一个C#控制台应用程序,添加对SimpleCom组件的引用,在主线程中创建SimpleCom组件的对象,在两个工作线程中同时调用该对象。

通过修改主线程的公寓类型,演示进程内COM组件对象在不同类型的公寓中的行为差异。

3.1 多线程公寓

在多线程公寓中创建SimpleCom组件的对象的代码如下:

 1 namespace ConsoleApplication1
2 {
3 class Program
4 {
5 [MTAThread()]
6 static void Main(string[] args)
7 {
8 var v = new ATLTestLib.SimpleCom();
9 Thread t = new Thread(x =>
10 {
11 Run((ATLTestLib.ISimpleCom)x);
12 });
13 t.SetApartmentState(ApartmentState.STA);
14 t.Start(v);
15 Thread.Sleep(300);
16 Thread t2 = new Thread(x =>
17 {
18 Run((ATLTestLib.ISimpleCom)x);
19 });
20 t2.SetApartmentState(ApartmentState.STA);
21 t2.Start(v);
22 }
23
24 static public void Run(ATLTestLib.ISimpleCom sc)
25 {
26 try
27 {
28 for (var i = 0; i < 5; i++)
29 {
30 Console.WriteLine(string.Format("[{0}] {1}",
31 Thread.CurrentThread.ManagedThreadId,
32 sc.Hello()));
33 }
34 }
35 catch (Exception ex)
36 {
37 Console.WriteLine(ex);
38 }
39 }
40 }
41 }

运行结果如下:

[3] 0>你好! 1>m_iMember = 0; 1>m_iMember = 1; 2>再见~
[5] 0>你好! 2>再见~
[3] 0>你好! 1>m_iMember = 2; 1>m_iMember = 3; 2>再见~
[5] 0>你好! 2>再见~
[3] 0>你好! 1>m_iMember = 4; 1>m_iMember = 5; 2>再见~
[5] 0>你好! 2>再见~
[3] 0>你好! 1>m_iMember = 6; 1>m_iMember = 7; 2>再见~
[5] 0>你好! 2>再见~
[3] 0>你好! 1>m_iMember = 8; 1>m_iMember = 9; 2>再见~
[5] 0>你好! 1>m_iMember = 8; 1>m_iMember = 9; 2>再见~2>再见~
请按任意键继续. . .

原理说明:

由于两个线程的代码能够同时调用组件对象v的方法,组件中m_str的值被两个线程同时修改,Hello方法返回的值出现了混乱,典型的缺乏的同步的结果。

3.2 单线程公寓

单线程公寓只需要将上面代码中的MTAThread改为STAThread即可。

输出如下:

[3] 0>你好! 1>m_iMember = 0; 2>再见~
[4] 0>你好! 1>m_iMember = 1; 2>再见~
[3] 0>你好! 1>m_iMember = 2; 2>再见~
[4] 0>你好! 1>m_iMember = 3; 2>再见~
[3] 0>你好! 1>m_iMember = 4; 2>再见~
[4] 0>你好! 1>m_iMember = 5; 2>再见~
[3] 0>你好! 1>m_iMember = 6; 2>再见~
[4] 0>你好! 1>m_iMember = 7; 2>再见~
[3] 0>你好! 1>m_iMember = 8; 2>再见~
[4] 0>你好! 1>m_iMember = 9; 2>再见~
请按任意键继续. . .

原理说明:

由于对STA中对象的调用都被COM运行时列集,自动对多线程调用实现了同步。

【转】 .NET中STAThread和MTAThread的更多相关文章

  1. .NET中STAThread和MTAThread

    本文讨论在.NET中使用进程内COM组件时的公寓模型,以一个示例直观演示STAThread和MTAThread的作用和区别. 1. COM中的公寓 1.1 基本规则 公寓是COM组件的运行环境,日常生 ...

  2. STAThread 和 MTAThread

    STAThread:single threaded apartment 直译过来是:单线程单元套间 MTAThread:multiple threaded apartment 直译过来是:多线程单元套 ...

  3. C#中[STAThread]的作用

    [STAThread]STAThread:Single Thread Apartment Thread.(单一线程单元线程)[]是用来表示Attributes: [STAThread]是一种线程模型, ...

  4. C#一些不太熟悉的类——扩展学习

    Process.CloseMainWindow Method 通过向进程的主窗口发送关闭消息来关闭拥有用户界面的进程. 注解 进程执行时,其消息循环处于等待状态. 每次操作系统将 Windows 消息 ...

  5. C#中一些默认的预定义属性

    C#中一些默认的预定义属性,见下表: 预定义的属性 有效目标 说明 AttributeUsage Class 指定另一个属性类的有效使用方式 CLSCompliant 全部 指出程序元素是否与CLS兼 ...

  6. AwaitAsync(异步和多线程)

    参考了一些大佬写的文章: https://www.cnblogs.com/yilezhu/p/10555849.html这个大佬写的文章,我还是很喜欢的 https://www.cnblogs.com ...

  7. Python开源框架

    info:更多Django信息url:https://www.oschina.net/p/djangodetail: Django 是 Python 编程语言驱动的一个开源模型-视图-控制器(MVC) ...

  8. C#中的stathread标签【待填的坑】

    stathread这种线程是给COM组件使用的线程,如果不适用com对象 如果com对象标记为sta的,则它就是单线程运行的 stathread 组件线程遗留的标签

  9. [STAThread]的含义

    Posted on 2007-07-07 10:06 桦林 阅读(33100) 评论(10) 编辑 收藏 [STAThread]STAThread:Single     Thread     Apar ...

随机推荐

  1. QT断点续传

    //功能:    根据一个URL地址将数据保存到指定路径下,支持断点续传//参数:    url            --需要访问的URL地址//         SavePath       -- ...

  2. tar --help

    pengdl@debian:~/test$ mkdir test1 pengdl@debian:~/test$ mkdir test2 pengdl@debian:~/test$ tar -xzf p ...

  3. c语言字符串实例

    例子:涉及字符串.字符.指针.++等 例一:字符串与字符 #include <stdio.h> void reverse(char *str) { char *end=str; print ...

  4. Android(java)学习笔记76:多线程-定时器概述和使用

    定时器: 定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行. 在Java中,可以通过Timer和TimerTask类来实现定义调度的功能 Timer public Tim ...

  5. 配置hibernate出现的错误一

    问题:2011-04-18 11:35:46,734 ERROR [org.hibernate.tool.hbm2ddl.SchemaUpdate] - could not complete sche ...

  6. php实现MVC

    在PHP中使用MVC越来越流行了,特别是在一些开源的框架当中.MVC足以应对大多数的情况,但还有一些情况是其不太适合的,如比较简单的个人博客,对于只有几百篇文章量级的博客,使用MVC让人觉得有些太复杂 ...

  7. [改善Java代码] 枚举项数量限定为64个以内

    建议89:枚举项的数量限制在64个以内 为了更好的使用枚举,java 提供了两个枚举集合:EnumSet和EnumMap,这两个集合的使用都比较简单,EnumSet表示其元素必须是某一枚举的枚举项,E ...

  8. VbCrlf的相关说明

    今天从别人发来的手顺书中发现了这几个字母,一头雾水,下面查了一下相关资料. vbCrLf是Visual Basic中的一个字符串常数,即“Chr(13) & Chr(10)”(回车符与换行符连 ...

  9. poj 2983 差分约束

    思路: 设dis[i]为标号为i的点到0号点的距离.对于P A B X,我们能得到等式dis[a]-dis[b]=x,那么可以化为两个不等式dis[a]-dis[b]>=x和dis[b]-dis ...

  10. Https要点

    http和https的区别 1.https协议需要到ca申请证书 2.http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议 3.http和https使用的是完全不同 ...