對 Windows 窗體控件進(jìn)行線(xiàn)程安全調用
今天在編寫(xiě)一個(gè)windows應用程序的時(shí)候碰到了一個(gè)小問(wèn)題,程序需求是這樣的,創(chuàng )建多個(gè)線(xiàn)程調用執行某個(gè)方法,WindowsForm中有個(gè)Progress Bar控件用于顯示已經(jīng)執行完畢的進(jìn)程數,即當所有的線(xiàn)程都運行完畢后,ProgressBar的進(jìn)度也到頭了。先給出初步的實(shí)現方式:
2 private int n = 0, count = 0; //實(shí)際線(xiàn)程數、已結束的線(xiàn)程數
3
4 private void StartTest()
5 {
6 n = int.Parse(txtThreadCount.Text); //線(xiàn)程數
7 progressBar1.Maximum = n * 10; //設置ProgressBar的最大值
8
9 Thread thread = null;
10 List<Thread> threads = new List<Thread>(MAXTHREAD);
11 ReadTableTest t = new ReadTableTest(tableName, fileds);
12 t.ThreadExitEvent += new ThreadExit(OnThreadExit); //線(xiàn)程執行完畢后通知主界面
13
14 try
15 {
16 //創(chuàng )建線(xiàn)程
17 for (int i = 0; i < n; i++)
18 {
19 thread = new Thread(new ThreadStart(t.Run));
20 threads.Add(thread);
21 }
22
23 //開(kāi)始調用接口
24 foreach (Thread tt in threads)
25 {
26 tt.Start();
27 }
28 }
29 catch (Exception ex)
30 {
31 MessageBox.Show(ex.Message);
32 }
33 }
34
35 //線(xiàn)程執行完畢后回調
36 public void OnThreadExit()
37 {
38 count++;
39 this.progressBar1.Value = count * 10; //設置ProgressBar的進(jìn)度
40
41 //判斷是否全部進(jìn)程已結束
42 if (count == n)
43 {
44 MessageBox.Show("所有線(xiàn)程已執行完畢!");
45 ClearState();
46 }
47 }
當調試執行這段程序時(shí)在this.progressBar1.Value = count * 10;處,報出了InvalidOperationException Cross-thread operation not valid異常,在網(wǎng)上搜索一番后,發(fā)現產(chǎn)生該問(wèn)題的原因如下。
問(wèn)題原因
由于 Windows窗體控件本質(zhì)上不是線(xiàn)程安全的。因此如果有兩個(gè)或多個(gè)線(xiàn)程適度操作某一控件的狀態(tài)(setvalue),則可能會(huì )迫使該控件進(jìn)入一種不一致的狀態(tài)。還可能出現其他與線(xiàn)程相關(guān)的bug,包括爭用和死鎖的情況。于是在調試器中運行應用程序時(shí),如果創(chuàng )建某控件的線(xiàn)程之外的其他線(xiàn)程試圖調用該控件,則調試器會(huì )引發(fā)一個(gè)InvalidOperationException
解決方案
對于該異常的解決方案有兩種,一種是關(guān)閉該異常檢測的方式來(lái)避免異常的出現,經(jīng)過(guò)測試發(fā)現此種方法雖然避免了異常的拋出,但是并不能保證程序運行結果的正確性,對于此例來(lái)說(shuō),經(jīng)常是全部線(xiàn)程結束時(shí),進(jìn)度條的顯示還未到頭呢。下面再來(lái)看看更加優(yōu)雅的解決方案,即通過(guò)保證線(xiàn)程的安全性來(lái)避免該異常,先來(lái)看看兩種方案的代碼。
方案1
2 {
3 InitializeComponent();
4 Control.CheckForIllegalCrossThreadCalls = false;
5 }
說(shuō)明
關(guān)閉CheckForIllegalCrossThreadCalls,這是Control class上的一個(gè)staticproperty,默認值為flase,目的在于開(kāi)關(guān)是否對Handle的可能存在的不一致存取的監測;且該項設置是具有Applicationscope的。
方案2
2 private delegate void SafeSetProgressBarValue(int v);
3
4 //線(xiàn)程執行完畢后回調
5 public void OnThreadExit()
6 {
7 count++;
8 OnSafeSetValue(count * 10); //使用線(xiàn)程安全的代碼設置ProgressBar的進(jìn)度
9
10 //判斷是否全部進(jìn)程已結束
11 if (count == n)
12 {
13 MessageBox.Show("所有線(xiàn)程已執行完畢!");
14 ClearState();
15 }
16 }
17
18 /// <summary>
19 /// 線(xiàn)程安全的修改ProgressBarValue方式。
20 /// </summary>
21 /// <param name="va"></param>
22 private void OnSafeSetValue(int va)
23 {
24
25 if (this.progressBar1.InvokeRequired)
26 {
27 SafeSetProgressBarValue call = delegate(int v) { this.progressBar1.Value = v; };
28
29 this.progressBar1.Invoke(call,va);
30 }
31 else
32 this.progressBar1.Value = va;
33 }
說(shuō)明
Windows窗體中的控件被綁定到特定的線(xiàn)程,不具備線(xiàn)程安全性。因此,如果從另一個(gè)線(xiàn)程調用控件的方法,那么必須使用控件的一個(gè) Invoke方法來(lái)將調用封送到適當的線(xiàn)程。該屬性可用于確定是否必須調用 Invoke方法,當不知道什么線(xiàn)程擁有控件時(shí)這很有用??丶嫌兴姆N方法可以安全地從任何線(xiàn)程進(jìn)行調用:Invoke、BeginInvoke、EndInvoke和 CreateGraphics。對于所有其他方法調用,當從另一個(gè)線(xiàn)程進(jìn)行調用時(shí),應使用這些 Invoke 方法之一。
Control.InvokeRequired 屬性
獲取一個(gè)值,該值指示調用方在對控件進(jìn)行方法調用時(shí)是否必須調用 Invoke 方法,因為調用方位于創(chuàng )建控件所在的線(xiàn)程以外的線(xiàn)程中。
屬性值
如果控件的 Handle 是在與調用線(xiàn)程不同的線(xiàn)程上創(chuàng )建的(說(shuō)明您必須通過(guò) Invoke 方法對控件進(jìn)行調用),則為 true;否則為 false。
更多資料:
http://msdn2.microsoft.com/zh-cn/library/ms171728(VS.80).aspx
http://msdn2.microsoft.com/zh-cn/library/system.windows.forms.control.invokerequired(VS.80).aspx
posted on 2007-02-08 20:47 shenfx 閱讀(679) 評論(0) 編輯 收藏 引用 網(wǎng)摘 所屬分類(lèi): 問(wèn)題解決

