對 Windows 窗體控件進(jìn)行線程安全調(diào)用
今天在編寫一個(gè)windows應(yīng)用程序的時(shí)候碰到了一個(gè)小問題,程序需求是這樣的,創(chuàng)建多個(gè)線程調(diào)用執(zhí)行某個(gè)方法,WindowsForm中有個(gè)Progress Bar控件用于顯示已經(jīng)執(zhí)行完畢的進(jìn)程數(shù),即當(dāng)所有的線程都運(yùn)行完畢后,ProgressBar的進(jìn)度也到頭了。先給出初步的實(shí)現(xiàn)方式:
2 private int n = 0, count = 0; //實(shí)際線程數(shù)、已結(jié)束的線程數(shù)
3
4 private void StartTest()
5 {
6 n = int.Parse(txtThreadCount.Text); //線程數(shù)
7 progressBar1.Maximum = n * 10; //設(shè)置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); //線程執(zhí)行完畢后通知主界面
13
14 try
15 {
16 //創(chuàng)建線程
17 for (int i = 0; i < n; i++)
18 {
19 thread = new Thread(new ThreadStart(t.Run));
20 threads.Add(thread);
21 }
22
23 //開始調(diào)用接口
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 //線程執(zhí)行完畢后回調(diào)
36 public void OnThreadExit()
37 {
38 count++;
39 this.progressBar1.Value = count * 10; //設(shè)置ProgressBar的進(jìn)度
40
41 //判斷是否全部進(jìn)程已結(jié)束
42 if (count == n)
43 {
44 MessageBox.Show("所有線程已執(zhí)行完畢!");
45 ClearState();
46 }
47 }
當(dāng)調(diào)試執(zhí)行這段程序時(shí)在this.progressBar1.Value = count * 10;處,報(bào)出了InvalidOperationException Cross-thread operation not valid異常,在網(wǎng)上搜索一番后,發(fā)現(xiàn)產(chǎn)生該問題的原因如下。
問題原因
由于 Windows窗體控件本質(zhì)上不是線程安全的。因此如果有兩個(gè)或多個(gè)線程適度操作某一控件的狀態(tài)(setvalue),則可能會(huì)迫使該控件進(jìn)入一種不一致的狀態(tài)。還可能出現(xiàn)其他與線程相關(guān)的bug,包括爭用和死鎖的情況。于是在調(diào)試器中運(yùn)行應(yīng)用程序時(shí),如果創(chuàng)建某控件的線程之外的其他線程試圖調(diào)用該控件,則調(diào)試器會(huì)引發(fā)一個(gè)InvalidOperationException
解決方案
對于該異常的解決方案有兩種,一種是關(guān)閉該異常檢測的方式來避免異常的出現(xiàn),經(jīng)過測試發(fā)現(xiàn)此種方法雖然避免了異常的拋出,但是并不能保證程序運(yùn)行結(jié)果的正確性,對于此例來說,經(jīng)常是全部線程結(jié)束時(shí),進(jìn)度條的顯示還未到頭呢。下面再來看看更加優(yōu)雅的解決方案,即通過保證線程的安全性來避免該異常,先來看看兩種方案的代碼。
方案1
2 {
3 InitializeComponent();
4 Control.CheckForIllegalCrossThreadCalls = false;
5 }
說明
關(guān)閉CheckForIllegalCrossThreadCalls,這是Control class上的一個(gè)staticproperty,默認(rèn)值為flase,目的在于開關(guān)是否對Handle的可能存在的不一致存取的監(jiān)測;且該項(xiàng)設(shè)置是具有Applicationscope的。
方案2
2 private delegate void SafeSetProgressBarValue(int v);
3
4 //線程執(zhí)行完畢后回調(diào)
5 public void OnThreadExit()
6 {
7 count++;
8 OnSafeSetValue(count * 10); //使用線程安全的代碼設(shè)置ProgressBar的進(jìn)度
9
10 //判斷是否全部進(jìn)程已結(jié)束
11 if (count == n)
12 {
13 MessageBox.Show("所有線程已執(zhí)行完畢!");
14 ClearState();
15 }
16 }
17
18 /// <summary>
19 /// 線程安全的修改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 }
說明
Windows窗體中的控件被綁定到特定的線程,不具備線程安全性。因此,如果從另一個(gè)線程調(diào)用控件的方法,那么必須使用控件的一個(gè) Invoke方法來將調(diào)用封送到適當(dāng)?shù)木€程。該屬性可用于確定是否必須調(diào)用 Invoke方法,當(dāng)不知道什么線程擁有控件時(shí)這很有用。控件上有四種方法可以安全地從任何線程進(jìn)行調(diào)用:Invoke、BeginInvoke、EndInvoke和 CreateGraphics。對于所有其他方法調(diào)用,當(dāng)從另一個(gè)線程進(jìn)行調(diào)用時(shí),應(yīng)使用這些 Invoke 方法之一。
Control.InvokeRequired 屬性
獲取一個(gè)值,該值指示調(diào)用方在對控件進(jìn)行方法調(diào)用時(shí)是否必須調(diào)用 Invoke 方法,因?yàn)檎{(diào)用方位于創(chuàng)建控件所在的線程以外的線程中。
屬性值
如果控件的 Handle 是在與調(diào)用線程不同的線程上創(chuàng)建的(說明您必須通過 Invoke 方法對控件進(jìn)行調(diào)用),則為 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)摘 所屬分類: 問題解決