處理WinForm多線程程序時的陷阱

與所有的UI開發平臺一樣,.NET下線程開發圖形界面同樣要遵循一個基本原則:就是對UI對象的操作一定要在產生該UI對象的線程裏進行(該線程稱作UI線程),因爲大部分UI對象都不是線程安全的。

.NET中,把調用調用放在UI線程裏執行是通過Form類及其子類的Invoke()方法實現的(具體的過程請參考其他資料),可以這樣做是因爲Form對象保存了創建它的線程的信息,而且Form類有一個bool類型的屬性InvokeRequired,可以通過它查看當前線程是否爲創建該Form對象的線程(UI線程)——如果爲true,則表示當前線程不是UI線程,反之則是。下面提供一個例子:

using System.Threading;

using System.Windows.Forms;

 

namespace csharpTest

{

     public class TestForm : Form

     {

         private Form form1;

         private Form form2;

 

         public static void Main()

         {

              TestForm tf = new TestForm();

              tf.Show();

              tf.UIThread();

              Application.Run();

         }

 

         public void UIThread()

         {

              form1 = new Form();

              form2 = new Form();

              form2.Show();//這裏是關鍵

              form1.Show();

              Thread thread = new Thread(new ThreadStart(WorkerThread));

              thread.Start();

         }

 

         public void WorkerThread()

         {

              if (form2.InvokeRequired)

                   form2.Invoke(new MethodInvoker(WorkerThread));

              else

              {

                   form1.Text = "This is from WorkerThread.";

              }

         }

 

         protected override void OnClosing(System.ComponentModel.CancelEventArgs e)

         {

              base.OnClosing (e);

              Application.Exit();

         }

     }

}

TestForm裏有兩個需要注意的方法,UIThread——用來模擬UI線程,WorkerThread——用來模擬用戶線程,UIThread中實例化了成員form1form2並調用了它們的Show方法,在WorkerThread中改變form1Text屬性。請注意WorkerThread裏有個技巧, if (form2.InvokeRequired) 即如果當前線程不是創建該form2的線程,則將方法通通過過Invoke方法放到UI線程裏去執行。但就是這裏問題出現了form1form2都是在UIThread裏建立的,所以它們保存的線程的信息應該是一樣的。所以form1.InvokeRquiredform2.InvokeRquired的值在任何線程裏都是一樣的,即在WorkerThreadInvokeRquire的值都應該是true(因爲在不同的線程裏)。但是如果註釋掉form2.Show()的話form2.InvokeRquiredWorkerThread中的值卻是false(在vs.net中調試看到),怎麼會這樣呢?而且如果不經過判斷直接在WorkerThread裏調用form2對象的Invoke的話…………居然會拋出異常——“在創建窗口句柄之前,不能在控件上調用 Invoke InvokeAsync

分析一下該異常的信息,在win32裏每一個窗體都有一個窗體句柄,是該窗體在建立時系統分配的,但我們確實在UI線程裏建立了form2對象的。這裏有個誤區.Net裏的Form對象並不是和win32的窗體對象完全對應的。本人竊以爲,產生一個Form類的實例時,只是產生了一個內存中的普通的對象,並不產生系統窗體(好像叫做User對象吧),只有它第一次呈現在屏幕上(或稱作創建)時,才產生系統裏表示窗體的User對象且分配句柄,對應的WIN32 APICreateWindow()方法大概也在這個時候執行(先聲明:本人對WIN32 AP 並不熟悉,所以這裏如果有什麼不妥的話請大家指正)

只有.NET裏的form對象調用某種方法使系統產生真正的窗體時,form纔會有創建它的線程的信息,且InvokeRquired纔有效,即才能調用formInvoke方法。不過我還沒弄清楚哪幾個方法可以做到。據我所知Show, CreateGraphics可以產生系統真正的系統窗體。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章