Q:子線程如何使用FolderBrowserDialog
A:
private void button1_Click(object sender, EventArgs e) ...{ System.Threading.Thread s = new System.Threading.Thread(new System.Threading.ThreadStart(test)); s.ApartmentState = System.Threading.ApartmentState.STA; s.Start(); } public void test() ...{ System.Windows.Forms.FolderBrowserDialog dlg = new FolderBrowserDialog(); dlg.ShowDialog(); }
以上代碼簡單的演示了FolderBrowserDialog在子線程中的使用,其中設置線程的ApartmentState為System.Threading.ApartmentState.STA是關鍵的語句。在.net2.0中應該使用
s.SetApartmentState(System.Threading.ApartmentState.STA);
如果沒有上述設置會報如下錯誤
在可以調用 OLE 之前,必須將當前線程設置為單線程單元(STA)模式。
如果你了解com的線程模型的話,應該已經清楚上面的問題根本了。
我們先看一下 FolderBrowserDialog的實現方法,這個控件實現是通過ole的.可以用Reflector.exe看一下他的代碼,調用了幾個windows shell32的api。
[SuppressUnmanagedCodeSecurity] internal class Shell32 ...{ // Methods public Shell32(); [DllImport("shell32.dll", CharSet=CharSet.Auto)] public static extern IntPtr SHBrowseForFolder([In] UnsafeNativeMethods.BROWSEINFO lpbi); [DllImport("shell32.dll")] public static extern int SHCreateShellItem(IntPtr pidlParent, IntPtr psfParent, IntPtr pidl, out FileDialogNative.IShellItem ppsi); public static int SHGetFolderPathEx(ref Guid rfid, uint dwFlags, IntPtr hToken, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszPath, uint cchPath); [DllImport("shell32.dll", EntryPoint="SHGetFolderPathEx")] private static extern int SHGetFolderPathExPrivate(ref Guid rfid, uint dwFlags, IntPtr hToken, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszPath, uint cchPath); [DllImport("shell32.dll")] public static extern int SHGetMalloc([Out, MarshalAs(UnmanagedType.LPArray)] UnsafeNativeMethods.IMalloc[] ppMalloc); [DllImport("shell32.dll", CharSet=CharSet.Auto)] public static extern bool SHGetPathFromIDList(IntPtr pidl, IntPtr pszPath); [DllImport("shell32.dll")] public static extern int SHGetSpecialFolderLocation(IntPtr hwnd, int csidl, ref IntPtr ppidl); [DllImport("shell32.dll")] public static extern int SHILCreateFromPath([MarshalAs(UnmanagedType.LPWStr)] string pszPath, out IntPtr ppIdl, ref uint rgflnOut); }
COM提供的線程模型共有三種:Single-Threaded Apartment(STA 單線程套間)、Multithreaded Apartment(MTA 多線程套間)和Neutral Apartment/Thread Neutral Apartment/Neutral Threaded Apartment(NA/TNA/NTA 中立線程套間,由COM+提供)。
STA 一個對象只能由一個線程訪問,相當於windows的消息循環,實現方式也是通過消息循環的,ActiveX控件、OLE文檔服務器等有界面的,都使用STA的套間。MTA 一個對象可以被多個線程訪問,即這個對象的代碼在自己的方法中實現了線程保護,保證可以正確改變自己的狀態。
所以創建和訪問一個activex或者ole對象時,必須設置線程模式為sta。
稍微有些多線程使用經驗的人會發現用Control.Invoke方法也可以成功調用ole對象,比如上面的例子改為
private void Form1_Load(object sender, EventArgs e) ...{ System.Threading.Thread s = new System.Threading.Thread(new System.Threading.ThreadStart(test)); //s.SetApartmentState(System.Threading.ApartmentState.STA); s.Start(); } public delegate void dtest(); public void test() ...{ this.Invoke(new dtest(invokeTest)); } public void invokeTest() ...{ System.Windows.Forms.FolderBrowserDialog dlg = new FolderBrowserDialog(); dlg.ShowDialog(); }
其實使得這個調用成功的原因不是在於Invoke,還是線程模式。如果把main函數上邊的[STAThread] 去掉的話,文章開始處的錯誤仍然會發生。Invoke只是讓主線程來執行子線程的調用函數。[STAThread]在程序入口處即將主線程置為sta模式,如果沒有這句話將置為mta模式。而且線程模型一旦確定將不可以更改,所以你無法在其他地方用代碼來設置主線程的線程模型。