COM STA/MTA の基礎知識 - スレッドモデルとハングを避ける考え方

· 更新日: · · COM, Windows開発, STA, MTA, スレッド

更新履歴(4件・最終更新 2026年08月02日)

この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。

記事の冒頭に「この記事の知識マップ」節を追加しました。本文で扱っている概念とその関係を、要約・図・詳細ページへのリンクにまとめたものです。本文の主張は変えていません。
外部レビュー(1283件)への対応として本文を更新しました。個々の変更内容は、この下の履歴を参照してください。
STAハングの再現コードで、再現しなかった場合もプロセスが終わらない状態になっていたのを直しました。STAスレッドは`done.WaitOne()`で待つフォアグラウンドスレッドなので、`obj.AnyMethod()`が返ってきた場合(agileなクラスだった、呼び出しが転送されなかった等)は誰も`done`を立てず、プロセスが終了しません。再現したときと再現しなかったときで、外から見える症状がどちらも「プロセスが終わらない」になり、再現条件を確かめる道具が条件の成否を隠していました。`try`/`finally`で`done.Set()`と`staThread.Join()`を必ず通すようにしています。
ハングする理由が複数箇所に散っていたのを6.2に集約し、重複した説明を図への参照に置き換えました。擬似コードだった再現例を、`CoInitializeEx`の宣言込みで`Type.GetTypeFromProgID`を使う実際に動く形に書き直し、再現条件と確認方法を表で明示しました。用語表と判断表を追加し、応答時間の数値は出典のある実測値ではない旨と、自分で測るための計測コードを添えました。
初版公開
この記事を引用する(DOI: 10.5281/zenodo.21589582)

この記事はZenodoにアーカイブされています。常に最新版へ解決されるDOIと、いま表示している版に固定されたDOIの両方を下に示します。

小村 豪(2026)「COM STA/MTA の基礎知識 - スレッドモデルとハングを避ける考え方」合同会社小村ソフト. https://doi.org/10.5281/zenodo.21589582 https://staging.comcomponent.com/blog/2026/01/31/000-sta-mta-com-relationship/

DOI(最新版)
10.5281/zenodo.21589582
DOI(この版)
10.5281/zenodo.21732607

COM の STA/MTA は、Windows 開発や .NET から COM を触るときに避けて通りにくい基礎知識です。 特に検索で多いのは、UI スレッドがなぜ STA なのか、Apartment をまたぐと何が起きるのか、なぜハングするのか、という疑問です。

目次


COMを使うとき、「どのスレッドで動くか」は避けて通れません。その中心にあるのが Apartment Model(STA/MTA) です。STA/MTAはWindowsの一般的なスレッド概念ではなく、COMオブジェクトの呼び出し規則を決めるためのスレッドモデルです。

この記事では、STAとMTAとCOMの関係を図にしながら、「なぜハングすることがあるのか」までつなげて説明します。

この記事の知識マップ

COMのSTA(Single-Threaded Apartment)とMTA(Multi-Threaded Apartment)は、どのスレッドからコンポーネントを呼んでよいかを決めるApartment Modelの2つの形です。STAは1スレッドに1Apartmentで、Apartmentを跨ぐ呼び出しはProxy/Stub経由でマーシャリングされます。別スレッドから呼ばれるSTAはメッセージループを回していないと呼び出しの転送を受け取れずハングし、同期呼び出し中にコールバックが来るパターンもデッドロックになりやすい構造です。MTAは複数スレッドで1Apartmentを共有する代わりに、オブジェクト側にスレッドセーフ設計を要求します。.NETの[STAThread]属性は、このApartment初期化を設定するラッパーにすぎません。

COM STA/MTAの知識マップSTAとMTAというCOMのApartmentモデルが、メッセージループ・マーシャリング・Proxy/Stubとどう関係し、メッセージループの不在がSTAのハングやデッドロックにつながる仕組みを示す図利用する前提とする前提とする利用する前提とする利用する利用する利用する前提とする利用するで構成できる原因になり得る防止する防止する原因になり得る原因になり得る利用する前提とする実装を担う実装を担うSTA(Single-Threaded Apartment)MTA(Multi-Threaded Apartment)COM(コンポーネントオブジェクトモデル)COMアパートメントモデル(STA/MTA)CoInitializeExメッセージループProxy/StubスレッドセーフなCOMオブジェクト設計Windows Forms.NETの[STAThread]/[MTAThread]属性ThreadingModelレジストリ値AutomationIDispatchMIDLSTAのハング(呼び出し転送の停止)MsgWaitForMultipleObjects同期呼び出し中のコールバックによるデッドロック.NET(Core以降)マーシャリング

図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全20件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle

1. まず結論(ひとことで)

  • COMオブジェクトは「どのApartmentに所属するか」で呼び出し規則が決まる
  • STAは 1スレッドに1Apartment、MTAは 複数スレッドで1Apartment と考えると理解しやすい
  • Apartmentを跨ぐ呼び出しは、COMがProxy/Stub経由でマーシャリングする

2. Apartment Modelの呼び出しパターン(図)

COMオブジェクトの呼び出しには、大きく3つのパターンがあります。

2.1. パターン1: 同一STAスレッド内での呼び出し

同じSTAスレッド内なら、直接呼び出しできます。オーバーヘッドなし。

STAスレッド直接呼び出し呼び出し元コードCOMオブジェクト

2.2. パターン2: 同一MTA内での呼び出し

MTA内の複数スレッドからは、どのスレッドからでも直接呼び出しできます。 ただしオブジェクト側はスレッドセーフ設計が必須

MTA(1つのApartment)直接呼び出し直接呼び出しワーカースレッド1COMオブジェクトワーカースレッド2

2.3. パターン3: Apartmentを跨ぐ呼び出し

異なるApartment間では、COMがProxy/Stubを使って転送します。 標準的なインターフェースならCOMランタイムが処理してくれます。

この先の表には、COM 特有の言葉が説明なしで出てきます。先に 1 行ずつ書いておきます。

用語 意味
マーシャリング Apartment やプロセスの境界を越えるときに、呼び出しと引数を「そのまま渡せる形」へ詰め替えて運ぶことです。境界の向こうで元の形に戻されます
Proxy / Stub マーシャリングを担当する 1 組の部品です。呼び出す側に立つのが Proxy(本物のふりをして呼び出しを受け取る)、呼ばれる側に立つのが Stub(受け取った呼び出しを本物のオブジェクトへ渡す)です
IDispatch メソッド名を文字列で問い合わせて、番号で呼び出すための COM インターフェースです。スクリプト言語や VBA から COM を使えるのは、この仕組みがあるためです
Automation IDispatch と、そこで使える限られたデータ型(BSTRVARIANT など)を前提にした COM の使い方の総称です。この範囲に収まっていると、マーシャリングは OS 側の oleaut32.dll が引き受けます
タイプライブラリ インターフェースの形(メソッド、引数の型)を機械可読な形で書いたデータです。.tlb ファイル単体か、DLL / EXE に埋め込まれた形で置かれます
タイプライブラリマーシャラー タイプライブラリを読んで、その場でマーシャリングを行う COM の標準機能です。専用の Proxy/Stub を作らなくても済むのはこれのおかげです
MIDL インターフェース定義(.idl)から Proxy/Stub のコードなどを生成する、Microsoft のコンパイラです

注意: Proxy/Stubは何でも自動で用意されるわけではないのですが、実務では明示的に生成しなくて済む場合がほとんどです。

パターン Proxy/Stubの準備
IDispatch ベース(Automation) 不要。oleaut32.dll が処理
タイプライブラリ登録済み 不要。タイプライブラリマーシャラーが処理
.NET COM Interop 通常は不要。タイプライブラリ経由で動く
IUnknown 直接派生のカスタムIF MIDLでProxy/Stub生成・登録が必要

つまり、MIDLでProxy/Stub生成が必要になるのは、IDispatch を使わず IUnknown 直接派生のインターフェースを作る場合です。 .NETやスクリプト言語から使う一般的なCOMコンポーネントでは、この作業が必要になることは少ないです。

MTAスレッドCOMランタイム(自動)STAスレッド呼び出し転送COMオブジェクトProxyRPC/IPCStub呼び出し元コード

ポイント: Apartmentを跨ぐとマーシャリングのオーバーヘッドが発生します。 高頻度の呼び出しでは性能に影響するため、設計時に考慮が必要です。

2.4. マーシャリングのオーバーヘッド目安

以下は一般的な目安です(実測値ではなく、状況・パラメータの複雑さで大きく変わります)。

呼び出しパターン 目安の時間 相対的な感覚
同一Apartment内(直接) 10〜100ナノ秒 通常の関数呼び出しとほぼ同じ
異なるApartment(同一プロセス) 1〜10マイクロ秒 直接呼び出しの100〜1000倍
異なるプロセス(Out-of-proc) 100〜1000マイクロ秒 直接呼び出しの1万〜10万倍

相対的な比較:

  • 同一Apartment: 1回のメモリアクセス程度
  • 異なるApartment: 1回のシステムコール程度
  • 異なるプロセス: ローカルホストへのネットワーク通信程度

ループで1万回呼ぶような場面では、この差が顕著に効いてきます。

この数値の扱いについて

上の表は 桁(オーダー)の感覚をつかむためのもので、出典のある実測値ではありません。 公開されている統一ベンチマークがあるわけでもないので、この数値そのものを設計判断の根拠に使わないでください。

一方で、「同一 Apartment 内は直接呼び出し、境界を越えると必ずマーシャリングが挟まる」という構造は、Microsoft のドキュメントに書かれている仕様 です。Single-Threaded Apartments の項には、同じ Apartment 内ならインターフェースポインタをマーシャリングなしで渡せること、Apartment をまたぐ場合は同一プロセス内であってもプロセス間と同じマーシャリングの仕組みを使うこと、そして呼び出しが隠しウィンドウ(OleMainThreadWndClass)へのウィンドウメッセージとして届くことが明記されています。桁が変わるのはこの構造が理由 で、数値そのものは環境と引数の複雑さで動きます。

自分のケースで判断したいときは、同じインターフェースを同一 Apartment から呼んだ場合と、別 Apartment から呼んだ場合で測り比べるのが確実です。

// C#。同じ呼び出しを N 回繰り返して 1 回あたりの時間を出す
static void Measure(string label, Action call, int iterations = 100_000)
{
    call(); // 初回の遅延(JIT、プロキシ生成、接続確立)を計測から外す

    var sw = System.Diagnostics.Stopwatch.StartNew();
    for (int i = 0; i < iterations; i++)
    {
        call();
    }
    sw.Stop();

    double perCallNs = sw.Elapsed.TotalMilliseconds * 1_000_000.0 / iterations;
    Console.WriteLine($"{label}: {perCallNs:F1} ns/call");
}

引数の少ないメソッドと、文字列や配列を渡すメソッドの両方で測ると、「マーシャリング量で効き方が変わる」ことも見えます。

3. STA(Single-Threaded Apartment)

STAは「1スレッド = 1Apartment」というモデルです。

  • そのApartment内のCOMオブジェクトは、基本的にそのスレッドでのみ実行
  • 別スレッドから呼ぶと、COMがメッセージキュー/RPC経由で呼び出しを転送
  • UIスレッド(WinForms/WPF)でよく使われる(UIも「1スレッド親和性+メッセージループ」なので相性が良い)

3.1. なぜUIスレッドでSTAが使われるのか

UIスレッドとSTAは設計が一致しているからです。

  • UIコントロールはスレッドセーフではない ボタンやテキストボックスなどは、生成したスレッドからしか安全に操作できない
  • STAも同じく「1スレッド親和性」 COMオブジェクトは生成したスレッドでのみ直接実行される
  • UIスレッドは必ずメッセージループを回す ウィンドウイベントを処理するために必須。STAの前提(メッセージポンプ)と一致する

だからWinForms/WPFのUIスレッドはデフォルトでSTAになっています。

ポイント: STAはスレッド親和性が高い代わりに、呼び出し元が多いと渋滞しやすい

4. MTA(Multi-Threaded Apartment)

MTAは「複数スレッドで1Apartment」というモデルです。

  • COMオブジェクトは複数スレッドから同時に呼び出される
  • オブジェクト側でスレッドセーフ設計が必須
  • サーバーサイド処理やバックグラウンド処理向き

ポイント: MTAは並列性が高いが、オブジェクト実装の責任が重い

5. STA/MTAはどこで決まるのか

COMのApartmentは、スレッドごとに初期化することで決まります。

  • CoInitialize / CoInitializeEx を呼んだ瞬間に、そのスレッドのApartmentが決まる
  • STA: COINIT_APARTMENTTHREADED
  • MTA: COINIT_MULTITHREADED

5.1. .NETでのSTA/MTA

.NETにも [STAThread] / [MTAThread] 属性や ApartmentState がありますが、これらはCOMのApartment Modelを設定するためのラッパーです。

  • [STAThread]Mainメソッド(エントリポイント)に付ける。COMを使う際にSTAとして初期化される
  • [MTAThread] → 同様にMainメソッド用。MTAとして初期化される
  • Thread.SetApartmentState(ApartmentState.STA)追加で作るスレッド用。スレッド開始前に設定が必要

注意点:

  • [STAThread] があっても、実際にCOMを呼ぶまでは初期化されない(COMを使わないなら効果なし)
  • 追加スレッドには [STAThread] は効かない。Thread.SetApartmentState を使う

つまり、.NETのSTA/MTAはCOMのSTA/MTAそのものであり、COM Interopのために用意された仕組みです。

重要: 後からApartmentを変更することはできません。最初の初期化が全てです。

6. STAを間違えると起きるハングの具体例

次のような構成は、実際にハングを引き起こしやすいです。

6.1. よくある状況

  • バックグラウンドでSTAスレッドを作成してCOMオブジェクトを生成
  • そのスレッドはメッセージループを回していない
  • 別スレッド(STA/MTA問わず)からそのCOMオブジェクトを呼び出す

6.2. 何が起きるのか

ハングの理由は、STAの2つの前提に集約できます。この節が理由の説明で、以降の節では繰り返しません。

  • COMオブジェクトは、生成したSTAスレッドで処理される 呼び出し元がSTAでもMTAでも、別スレッドからの呼び出しは必ずそのSTAスレッドへ転送されます。転送は、COMがそのApartmentに作る隠しウィンドウ(ウィンドウクラス OleMainThreadWndClass)へのウィンドウメッセージとして届きます
  • その転送を受け取るには、STAスレッドがメッセージポンプを回している必要がある Microsoft のドキュメントでも「各STAは、他のプロセスや同一プロセス内の他のApartmentからの呼び出しを処理するために、メッセージループを持たなければならない」と明記されています

したがって、メッセージを回していないSTAスレッドは呼び出しを受け取れず、呼び出し元は返事を待ち続け、結果としてハングします。

なお、これは .NET でも同じです。Single-Threaded Apartments のドキュメントには、STAスレッドを Task.Wait()Task.ResultThread.Sleep()ManualResetEvent.WaitOne() などでブロックすると、COMのコールバックやApartmentをまたぐ呼び出しが完了できずデッドロックになる、という警告が置かれています。次の 6.3 の失敗例が WaitOne() で止まっているのは、まさにこの形です。

一方、UIスレッドはウィンドウイベントを処理するために最初からメッセージループを回しているので、STAの要件を追加実装なしで満たしています。UIスレッドがSTAのCOMオブジェクトを動かす場所として自然な選択肢になるのは、このためです。

6.3. 擬似コード(典型的な失敗パターン)

using System;
using System.Runtime.InteropServices;
using System.Threading;

internal static class StaHangDemo
{
    private const uint COINIT_APARTMENTTHREADED = 0x2;

    [DllImport("ole32.dll")]
    private static extern int CoInitializeEx(IntPtr pvReserved, uint dwCoInit);

    [DllImport("ole32.dll")]
    private static extern void CoUninitialize();

    public static void Run(string progId)
    {
        var ready = new AutoResetEvent(false);
        var done = new AutoResetEvent(false);

        object comObj = null;

        var staThread = new Thread(() =>
        {
            // STAとして初期化
            CoInitializeEx(IntPtr.Zero, COINIT_APARTMENTTHREADED);

            // ThreadingModel=Apartment で登録された COM クラスを想定
            Type type = Type.GetTypeFromProgID(progId, throwOnError: true);
            comObj = Activator.CreateInstance(type);
            ready.Set();

            // メッセージループがないまま待機 -> ここが致命傷
            done.WaitOne();

            CoUninitialize();
        });

        staThread.SetApartmentState(ApartmentState.STA);
        staThread.Start();

        ready.WaitOne();

        try
        {
            // 別スレッド(STA/MTA問わず)から呼ぶと、呼び出しがSTAに転送される
            // しかしSTA側はメッセージを処理しないため、ここでハングしやすい
            dynamic obj = comObj;
            obj.AnyMethod();
        }
        finally
        {
            // 呼び出しが返ってきた場合 ── agileなクラスだった、
            // 呼び出しが転送されなかった、AnyMethodが即座に返った ── でも、
            // 必ずSTAスレッドを解放する。ここを省くと、doneを待ち続ける
            // フォアグラウンドスレッドが残り、「再現しなかった」ときも
            // プロセスが終わらない。再現の有無を症状で見分けられなくなる
            done.Set();
            staThread.Join();
        }
    }
}

このコードは、ThreadingModel=Apartment(= STA)として登録された COM クラスの ProgID を渡す前提です。AnyMethod は、そのクラスが実際に持つメソッド名に置き換えてください。どの COM クラスでも再現するわけではありません。 再現に必要な条件は次の 3 つです。

条件 確認方法
対象クラスの ThreadingModelApartment レジストリの HKEY_CLASSES_ROOT\CLSID\{CLSID}\InprocServer32ThreadingModel 値を見ます。BothFree だと呼び出しが転送されず、再現しません
STAスレッドがメッセージを回していない 上の例の done.WaitOne() がそれにあたります
呼び出しを 別のスレッド から行う 同じSTAスレッド内から呼ぶ限り直接呼び出しになるので、ハングしません

obj.AnyMethod()try / finally で挟み、done.Set()staThread.Join() を必ず通しているのは、この「再現しないケース」を見分けるためです。STAスレッドは既定でフォアグラウンドスレッドなので、done が立たない限りプロセスは終わりません。finally を省くと、再現したとき(呼び出しが返らない)と再現しなかったとき(呼び出しが返ったが done を誰も立てない)で、外から見える症状がどちらも「プロセスが終わらない」になります。再現条件を確かめるための道具が、条件の成否を隠してしまうわけです。上の形なら、再現しなかった場合はそのまま正常終了します。

止まったことの確認は、デバッガでプロセスを一時停止し、呼び出し元スレッドのスタックが COM の待機で止まっていること、STAスレッドが WaitOne で止まっていることを見るのが早いです。

COMランタイムSTAスレッドメインスレッドCOMランタイムSTAスレッドメインスレッドメッセージループなしここで詰まっているメッセージで転送するが...WaitOne中なのでメッセージを処理できない呼び出し元も待ち続ける両方が待ち状態 → ハングスレッド開始CoInitializeEx(STA)COMオブジェクト生成ready.Set()done.WaitOne()で待機CallComObject()呼び出しを転送しようとする

図の中央、「メッセージで転送するが…」の 2 行が、6.2 で書いた 2 つの前提が崩れている場所です。

6.4. 回避の要点

  • 別スレッドからの呼び出しを受ける場合、STAスレッドはメッセージループを回す必要がある
  • 可能ならUIスレッド上で生成・利用する(UIスレッドは最初からメッセージループがある)
  • STAが不要なら最初からMTAにする

補足: 同一スレッド内だけで完結するなら、常に Application.Run() が必要とは限りません。 ただし、UI系・COM系は別スレッドからの呼び出しが絡むことが多いため、実務上はほぼ必須です。

6.5.「メッセージループを回す」って結局なに?

Win32のUIスレッドがやっている、例のこれです。

while (GetMessage(out var msg, IntPtr.Zero, 0, 0))
{
    TranslateMessage(ref msg);
    DispatchMessage(ref msg);
}

STAでは、別スレッドからの呼び出しが「転送」されてきます。 その転送を受け取って実行に回すのが、このループ(メッセージポンプ)だ、という話です。

6.6. 正しい方向の例(雑に書くとこう)

「バックグラウンドSTAでCOMを使いたい」なら、こういう形になります。

var ready = new AutoResetEvent(false);
object comObj = null;

var staThread = new Thread(() =>
{
    CoInitializeEx(IntPtr.Zero, COINIT_APARTMENTTHREADED);

    comObj = new SomeStaComObject();
    ready.Set();

    // STAスレッドが生きている間はメッセージを回す
    Application.Run();

    CoUninitialize();
});

staThread.SetApartmentState(ApartmentState.STA);
staThread.Start();

ready.WaitOne();
CallComObject(comObj);

(※ CoInitializeEx / CoUninitialize の呼び忘れは普通に事故ります。CoInitializeEx の P/Invoke 宣言は 6.3 と同じものが要ります)

Application.Run() の引数なしの形は、フォームを持たずにメッセージループだけを回す ためのものです。用途としてはここがまさに想定される場面ですが、次の 2 点は押さえておいてください。

  • System.Windows.Forms への参照が要ります。コンソールアプリなら、プロジェクトファイルに <UseWindowsForms>true</UseWindowsForms> を足します
  • このループは自分では終わりません。 止めるには、そのスレッド上で Application.ExitThread()(またはアプリ全体を終える Application.Exit())を呼びます。上の例で CoUninitialize() まで到達させたいなら、STAスレッドへ「終了」を伝えて ExitThread を呼ばせる仕組みが別に必要です

WinForms に依存したくない場合は、6.5 の GetMessage / DispatchMessage ループを自分で書くか、メッセージと同期オブジェクトの両方を待てる MsgWaitForMultipleObjects を使います。後者は「イベントで終了通知を受けつつ、COM の呼び出しも取りこぼさない」形にしたいときの定番です。

6.7. もう一つのハング例: 同期呼び出し中のコールバック

STAは「呼び出しが転送される」だけでなく、状況によっては逆方向(サーバー→クライアント)にコールバックが来ます。中でも同期呼び出し中にコールバックが発生するパターンは、デッドロックの定番です。

COMサーバーUIスレッド(STA)COMサーバーUIスレッド(STA)DoWorkの戻りを待っている(メッセージを処理していない)待機中なのでコールバックを受け取れないコールバックの完了を待っているお互いが相手を待っている → デッドロックDoWork()(同期呼び出し)ProgressCallback()(コールバック)

なぜデッドロックになりやすいのか:

  1. UIスレッドが DoWork()同期呼び出し(ブロッキング)
  2. UIスレッドは戻りを待っている(メッセージを処理していない)
  3. サーバーが ProgressCallback() をUIスレッドに送る
  4. UIスレッドは待機中なのでコールバックを受け取れない
  5. サーバーはコールバックの完了を待っている
  6. お互いが相手を待っている → 永遠に進まない

処理時間の長さは関係ありません。同期呼び出し中にコールバックが来るというパターン自体が問題になりやすいです。

補足: COMには状況によってメッセージを回す・再入する仕組みもあり、コンポーネントや呼び出し形態で挙動が変わります。 必ずデッドロックになるわけではありませんが、このパターンは避けるのが無難です。

7. ざっくり使い分け

状況 選ぶもの 判断の理由 あわせて必要なこと
UIが絡む(WinForms / WPF) STA UIコントロールが1スレッド親和性で、UIスレッドは元からメッセージループを持つため(3.1) 特になし。既定でSTAです
大量の並列処理をさせたい MTA 複数スレッドが1つのApartmentを共有し、転送なしで直接呼べるため(2.2) COMオブジェクト側のスレッドセーフ設計。呼ぶ側の排他ではなく、オブジェクト実装側の責任です
バックグラウンドでSTAのCOMを使いたい STA + メッセージループ 別スレッドから呼ばれる以上、転送を受ける口が要るため(6.2) Application.Run()GetMessage ループ。終了手段(ExitThread など)も一緒に設計します(6.6)
使うCOMコンポーネントの要求が決まっている 相手に合わせる Apartmentは呼び出し規則そのもので、後から変えられないため(5章) ThreadingModel の値を確認します。Apartment ならSTA前提で組みます
高頻度で呼び出す 呼び出し側と同じApartmentへ寄せる 境界を越えるたびにマーシャリングが挟まるため(2.4) 難しければ、呼び出し回数自体を減らす(まとめて渡す)設計にします

迷ったときの優先順位は、「相手の要求 > UIの有無 > 並列性」 の順で見ると決まりやすいです。Apartmentは最初の初期化で確定して後から変更できないので、ここだけは実装を始める前に決めておきます。

8. まとめ

STA/MTAはCOMのためのスレッドモデルで、STAは1スレッド = 1Apartment、MTAは複数スレッドで1Apartmentという形をとります。Apartmentを跨ぐ呼び出しはCOMがProxy/Stub経由で転送してくれます(標準IF以外はMIDL等での生成・登録が必要)が、そこにはマーシャリングのオーバーヘッドが伴うため、高頻度の呼び出しが想定される場面ではApartment設計を慎重に決めたいところです。

ハングの観点では、「別スレッドからの呼び出しを受けるSTAスレッドは、メッセージポンプを回すことが前提」という一点に尽きます。メッセージを回していないSTAスレッドに呼び出すとハングしやすく、同期呼び出し中にコールバックが来るパターンもデッドロックになりやすい。UIスレッドは「1スレッド親和性」と「メッセージループ」を最初から持っているため、この前提を追加実装なしで満たしており、STAのCOMと相性が良いわけです。

9. 参考資料

  • Apartment Model https://learn.microsoft.com/en-us/windows/win32/com/com-apartments
  • CoInitializeEx https://learn.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitializeex
  • Single-Threaded Apartments(メッセージループが必須である理由、隠しウィンドウ、.NETでのデッドロック警告) https://learn.microsoft.com/en-us/windows/win32/com/single-threaded-apartments
  • Multithreaded Apartments https://learn.microsoft.com/en-us/windows/win32/com/multithreaded-apartments
  • InprocServer32(ThreadingModel の値) https://learn.microsoft.com/en-us/windows/win32/com/inprocserver32
  • Application.Run メソッド(フォームなしでメッセージループを回す形と、その止め方) https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.application.run
  • MsgWaitForMultipleObjects(メッセージと同期オブジェクトを同時に待つ) https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-msgwaitformultipleobjects

この記事のWordファイルをダウンロード

同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。

このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。

この記事は次のサービスページにつながります。近い入口からご覧ください。

よくある質問

この記事のテーマについて、相談時によくある質問をまとめています。

STAとMTAはどちらを選ぶべきですか?
UIが絡む処理ならSTA、大量の並列処理ならMTAが基本の使い分けです。STAは1スレッドに1Apartmentという形でスレッド親和性が高い一方、呼び出し元が多いと渋滞しやすくなります。MTAは複数スレッドで1Apartmentを共有するため並列性が高いですが、COMオブジェクト側にスレッドセーフ設計が必須になります。どちらでもない場合は、使う既存ライブラリやCOMサーバーの要求に合わせるのが現実的です。
なぜUIスレッドはSTAなのですか?
UIスレッドとSTAは設計が一致しているためです。ボタンやテキストボックスなどのUIコントロールはスレッドセーフではなく、生成したスレッドからしか安全に操作できません。STAも同じく1スレッド親和性のモデルです。さらにUIスレッドはウィンドウイベント処理のために必ずメッセージループを回しており、STAの前提であるメッセージポンプを追加実装なしで満たします。このためWinForms/WPFのUIスレッドはデフォルトでSTAになっています。
STAのCOMオブジェクトを呼ぶとハングするのはなぜですか?
STAのCOMオブジェクトへの呼び出しは、生成したSTAスレッドで処理されます。別スレッドからの呼び出しはCOMがメッセージ/RPC経由で転送しますが、STAスレッドがメッセージループを回していないと転送を受け取れず、呼び出し元が待ち続けてハングします。回避するには、別スレッドから呼ばれるSTAスレッドでメッセージループを回すか、UIスレッド上で生成・利用するか、STAが不要なら最初からMTAにすることです。
.NETの[STAThread]属性は何のためにありますか?
COMのApartment Modelを設定するためのラッパーです。Mainメソッドに付けると、COMを使う際にそのスレッドがSTAとして初期化されます。ただし実際にCOMを呼ぶまでは初期化されず、COMを使わないアプリでは効果がありません。また追加で作るスレッドには効かないため、スレッド開始前にThread.SetApartmentStateで設定します。Apartmentは最初の初期化で決まり、後から変更できない点にも注意が必要です。

著者プロフィール

記事の著者プロフィールページです。

小村 豪

合同会社小村ソフト 代表

Windows ソフト開発、技術相談、不具合調査を中心に、既存資産が残る案件や原因が見えにくい障害調査に強みがあります。

ブログ一覧に戻る