在 WinForms 應用程式中,若於背景執行緒直接呼叫 FolderBrowserDialog.ShowDialog(),將拋出經典例外:
「在可以調用 OLE 之前,必須將當前線程設置為單線程單元(STA)模式。」
關鍵在於啟動執行緒前將 ApartmentState 設為 STA:
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(ShowFolderDialog));
t.SetApartmentState(ApartmentState.STA); // .NET 2.0+ 必須使用此方法
t.Start();
}
public void ShowFolderDialog()
{
using (FolderBrowserDialog dlg = new FolderBrowserDialog())
{
dlg.ShowDialog();
}
}
COM 定義三種執行緒模型:
| 模型 | 全稱 | 特性 | 典型應用 |
|---|---|---|---|
| STA | Single-Threaded Apartment | 單執行緒存取,透過訊息迴圈封送呼叫 | ActiveX 控制項、OLE 文件伺服器、所有有 UI 的 COM 物件 |
| MTA | Multithreaded Apartment | 多執行緒並存存取,物件自行實作同步 | 無 UI 的商務邏輯物件 |
| NTA | Neutral Threaded Apartment | 中立套間,由 COM+ 提供 | 高效能伺服器端元件 |
反編譯可見,FolderBrowserDialog 內部呼叫 Shell32.dll 的非受控 API:
[DllImport("shell32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr SHBrowseForFolder([In] BROWSEINFO lpbi);
// 其他 SHGetPathFromIDList、SHGetMalloc 等
這些都是傳統 COM 介面,強制要求 STA 執行緒。
public void test()
{
this.Invoke(new Action(() =>
{
using (FolderBrowserDialog dlg = new FolderBrowserDialog())
{
dlg.ShowDialog();
}
}));
}
本質原因:Invoke 將委派封送至主 UI 執行緒執行,而主執行緒在 Main() 標記 [STAThread] 時已是 STA。若移除 [STAThread],主執行緒變為 MTA,Invoke 同樣失敗。
using 確保 Dispose() 呼叫