【转】 .NET中STAThread和MTAThread

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运行时列集,自动对多线程调用实现了同步。

时间: 2024-11-05 16:29:34

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

C#中两个窗体将相互跳转

首先 program.cs 中 [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); // 自动生成的代码是这样的 // Application.Run(new Form1()); // 表示 实例化一个新的 Form1 并显示之 此时程序进入消息循环 // 一旦 Form1 被关闭则程序也随之关闭了 // 为

AwaitAsync(异步和多线程)

参考了一些大佬写的文章: https://www.cnblogs.com/yilezhu/p/10555849.html这个大佬写的文章,我还是很喜欢的 https://www.cnblogs.com/mushroom/p/4575417.html https://blog.csdn.net/dyllove98/article/details/9735955这篇文章是讨论在.NET中使用进程内COM组件时的公寓模型,以一个示例直观演示STAThread和MTAThread的作用和区别. C#5.

C#中的一些细节

localhost与127.0.0.1的区别 127.0.0.1是一个回送地址,指本地机,一般用来测试使用.大家常用来ping 127.0.0.1来看本地ip/tcp正不正常,如能ping通即可正常使用. 对于大多数习惯用localhost的来说,实质上就是指向127.0.0.1这个本地IP地址.在操作系统中有个配置文件将localhost与127.0.0.1绑定在了一起.可以理解为本地主机的意思. localhost与127.0.0.1的区别是什么? localhost也叫local ,正确的

Key/Value之王Memcached初探:二、Memcached在.Net中的基本操作

一.Memcached ClientLib For .Net 首先,不得不说,许多语言都实现了连接Memcached的客户端,其中以Perl.PHP为主. 仅仅memcached网站上列出的语言就有:Perl.PHP.Python.Ruby.C#.C/C++以及Lua等. 那么,我们作为.Net码农,自然是使用C#.既然Memcached客户端有.Net版,那我们就去下载一个来试试. 下载文件:http://pan.baidu.com/s/1w9Q8I memcached clientlib项目

C#基础系列:实现自己的ORM(反射以及Attribute在ORM中的应用)

反射以及Attribute在ORM中的应用 一. 反射什么是反射?简单点吧,反射就是在运行时动态获取对象信息的方法,比如运行时知道对象有哪些属性,方法,委托等等等等.反射有什么用呢?反射不但让你在运行是获取对象的信息,还提供运行时动态调用对象方法以及动态设置.获取属性等的能力.反射在ORM中有什么用呢?我这里所讨论的ORM实现是通过自定义Attribute的方式进行映射规则的描述的.但是我们并不知道具体哪个对象需要对应哪个表,并且这些对象是独立于我们的ORM框架的,所以我们只能通过自定义Attr

【转载】Memcached在.Net中的基本操作

一.Memcached ClientLib For .Net 首先,不得不说,许多语言都实现了连接Memcached的客户端,其中以Perl.PHP为主. 仅仅memcached网站上列出的语言就有:Perl.PHP.Python.Ruby.C#.C/C++以及Lua等. 那么,我们作为.Net码农,自然是使用C#.既然Memcached客户端有.Net版,那我们就去下载一个来试试. 下载文件:http://pan.baidu.com/s/1w9Q8I memcached clientlib项目

在.NET中如何取得代码行数

文章目的 介绍在.NET中取得代码行数的方法 代码 [STAThread] static void Main(string[] args) { ReportError("Yay!"); } static private void ReportError(string Message) { StackFrame CallStack = new StackFrame(1, true); Console.Write("Error: " + Message + "

在.NET中怎样取得代码行数

文章目的 介绍在.NET中取得代码行数的方法 代码 [STAThread] static void Main(string[] args) { ReportError("Yay!"); } static private void ReportError(string Message) { StackFrame CallStack = new StackFrame(1, true); Console.Write("Error: " + Message + "

关于.Net中Process的使用方法和各种用途汇总

简介: .Net中Process类功能十分强大.它可以接受程序路径启动程序,接受文件路径使用默认程序打开文件,接受超链接自动使用默认浏览器打开链接,或者打开指定文件夹等等功能. 想要使用Process类之前,需要先引用using System.Diagnostics; Process类用法1: Process类有很多种使用方式,比如我只想打开一个应用程序的话,直接调用Process.Start("应用的路径");即可打开对用的应用程序,用默认程序打开文件,打开超链接,打开文件夹也都只用