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模式。而且線程模型一旦確定將不可以更改,所以你無法在其他地方用代碼來設置主線程的線程模型。