読者です 読者をやめる 読者になる 読者になる
ようこそ。睡眠不足なプログラマのチラ裏です。

アパートメントモデルと、.NETのSTAThreadAttribute


アーパートメントモデルの種類

COMコンポーネント*1は、大きくシングルスレッドアパートメントモデル(STA)と
マルチスレッドアパートメントモデル(MTA)に分けることができる。
レンタルスレッドアパートメントモデル(RTA)というのもあるようですが、詳細不明のため割愛します。

■シングルスレッドアパートメント(Single Thread Apartment)


シングルスレッドアパートメントは1つのスレッドから構成されているので、
シングルスレッドアパートメントの中に存在しているすべての COM オブジェクトは、
そのアパートメントに属する1つのスレッドからのメソッド呼び出ししか受け取ることができません。
シングルスレッドアパートメントの中の COM オブジェクトに対するすべてのメソッド呼び出しは、
そのシングルスレッドアパートメントのスレッドのウィンドウ メッセージ キューによって同期されます。


OLE*2を使用するすべてのスレッドは、それぞれ独自のアパートメントの中に存在し、
OLE は着信したすべての呼び出しをウィンドウ メッセージ キューを使って同期させます。
1つの実行スレッドを持つプロセスは、このモデルの特殊なケースと見なすことができます。

■マルチスレッド アパートメント(Multi Thread Apartment)


マルチスレッドアパートメントは、1つまたは複数のスレッドから構成されているので、
マルチスレッドアパートメントの中に存在しているすべての COM オブジェクトは、
そのマルチスレッドアパートメントに属する任意のスレッドから、メソッド呼び出しを直接に受け取ることができます。
マルチスレッドアパートメントの中のスレッドはフリー スレッディングと呼ばれるモデルを使用します。
OLE は、MTA の中の COM オブジェクトに対するメソッド呼び出しに関しては同期処理をいっさい行わない。
したがって、COMオブジェクトは、必要ならば同期処理を独自に用意しなければなりません
つまり、コンポーネントの開発者はクリティカル セクション、ミューテックス、またはセマフォを使って
コンポーネントの中のスレッドをシリアル化し、スレッドが終了状態を通知するのを待たなくてはなりません。



「COM スレッディング入門」


アパートについて
COMオブジェクトは、必ずアパートと呼ばれる単位の中で実行されます。


STAモデルの場合、1つのアパートに含まれているCOMオブジェクトは同時に実行されないことが保証されます。
具体的には、外部からメソッドを呼び出す要求をキューに格納する仕組みがアパートメントの境界に用意されていて、
外部からのメソッド呼び出しに対しては、必ずこのキューを経由することになるので、スレッドセーフであることが保証される。


一方のMTAモデルの場合には、アパートに入っているCOMオブジェクトは、
複数のスレッドによって同時に呼び出される可能性がある。
そのため、MTAモデルをサポートするCOMオブジェクトは、メソッドの呼び出しを同時に受けたとしても、
問題なく動作するような仕組みを備えてる必要があります。
つまりMTAモデルを採用する場合、独自にスレッドを管理し、スレッドセーフであることを保証するための
プログラミングをしなければならないことを意味します。



C# および VB.NETのMain()メソッドは何故STAThreadAttributeがデフォルトなのか

何故C# および VB.NETのMain()メソッドはSTAThreadAttributeがデフォルトなのかというと、
.NET Frameworkの一部の機能で必要なCOMを使用する際に、STAである必要があるからでしょう。
つまり、COM 相互運用機能においてスレッドセーフを保証するためにSTAThreadAttributeを指定しているわけですね。


以下、STAThreadAttributeでなはいとき発生する不具合

・ドラッグ&ドロップの使用に関する不具合
・リフレクションを利用したメソッド呼び出しに関する不具合
クリップボードを使ったコピー&ペーストに関する不具合
・FileDialogクラスの使用に関する不具合
・RichTextBox等のコントロールの使用に関する不具合


Windows フォームは、シングルスレッド アパートメント (STA) モデル

FileDialogクラスの使用に関する不具合は実際に経験していて、
UIスレッドとは別のスレッド(コールバックを利用した非同期メソッド)から
System.Windows.Forms.SaveFileDialogクラスをインスタンス化してShowDialog()メソッドを呼び出したとき、
マイコンピュータ内のオブジェクトが参照できなくなるという不具合に見舞われました。

■Windowsフォームは、シングルスレッドアパートメント


Windows フォームは、本来アパートメントスレッドであるネイティブな Win32 ウィンドウに基づいているため、
シングルスレッドアパートメント (STA) モデルを使用します。STA モデルでは、任意のスレッドでウィンドウを作成できるが、
ウィンドウの作成後にスレッドを切り替えることはできない。
したがって、ウィンドウに対するすべての関数呼び出しは、そのウィンドウを作成したスレッドで実行される必要があります。
.NET Framework のクラスは、Windows フォームの外側ではフリースレッドモデルを使用します。


STA モデルでは、コントロールを作成したスレッドの外部から呼び出される必要があるすべてのメソッドを、
そのコントロールの作成スレッドにマーシャリングする (そのコントロールの作成スレッドで実行する) 必要があります。
基本クラス Control には、この目的のためのメソッド (Invoke、BeginInvoke、および EndInvoke) が用意されています。
Invoke は同期メソッド呼び出しを実行し、BeginInvoke は非同期メソッド呼び出しを実行します。

つまり、System.Windows.Forms.SaveFileDialogクラスはSTAモデルであり、
任意のスレッドでウィンドウを作成することができるが、ウィンドウの作成後にスレッドを切り替えることはできないので、
ウィンドウに対するすべての関数呼び出しは、そのウィンドウを作成したスレッドで実行される必要があるというわけです。
つまり、このような場合、UIスレッドからDelegateによって、SaveFileDialogを呼び出すメソッドを
Invokeすることで解決することができます。MSDNにも「インスタンスのメンバの場合は、スレッドセーフであるとは限りません。」
と書いてありますし、当たり前っちゃー当たり前ですよね(´・ω・`)



ちなみに、STAThreadAttributeのような、System.Attributeクラスを継承した派生クラスは、
.NET Framework1.1の時点で195個ある。.NET Framework2.0にいたっては、328個とほぼ倍増となっているので、
そのすべてを覚えきるのは・・・、とりあえずは重要なところだけ掻い摘んで覚えようか(`・ω・´)

*1:Component Object Modelの略称で、コンポーネントと言うプログラムの単位で管理しています。このコンポーネント内で何がされていようとCOMには関係はありません。COMはこのコンポーネント同士のつながりを決めている規格。またDCOMはDistributed COMの略称でCOMを分散環境でも使えるように拡張した規格です。ちなみにDCOMの提唱はMicrosoftがしました。COMはサーバークライアントモデルになっていて、とあるCOMコンポーネントが実行されるとそれがサーバーとして機能します。そのサーバーは、呼び出した側(クライアント)の領域で実行されたり、別領域で実行されたりします。この時、クライアントはサーバーの利用方法さえわかっていれば、サーバーがどのように動作しているかは感知する必要はないです。サーバーは複数のクライアントから共有されたり、また別のCOMコンポーネントを利用したりするかもしれませんけど、それはサーバー側が知っていればいいだけで、クライアントで意識しなくても良くなってます。

*2:Object Linking and Embedding:Windowsにおいて、アプリケーションソフト間でデータを転送・共有するための仕組み。OLEを使うことによって、あるアプリケーションソフトで作成している文書の中に、別のアプリケーションソフトで作成した情報を埋め込んだり、別のアプリケーションソフトの機能をあたかも自分の機能であるかのように提供することができるようになる。1991年に登場したOLE 1.0はWindows 3.1に搭載された。大幅に機能を拡張したOLE2が1993年に登場し、Windows 95に搭載された。1996年にはインターネットに対応するための機能を追加したものが登場し、名称も「ActiveX」に改められた。