我們在做winform應用的時候,大部分情況下都會碰到使用多線程控制界面上控件信息的問題,隨之就極有可能出現這個異常:Cross-thread operation not valid:Control 'textBox1' accessed from a thread other than the thread it was created on。
在網上看了一些解決方案,第一個就是在winfrom的load方法里加入這行:Control.CheckForIllegalCrossThreadCalls =false;(不推薦)
這句代碼就是說在這個類中我們不檢查跨線程的調用是否合法(如果沒有加這句話運行也沒有異常,那么說明系統以及默認的采用了不檢查的方式)。然而,這種方法不可取。我們查看CheckForIllegalCrossThreadCalls 這個屬性的定義,就會發現它是一個static的,也就是說無論我們在項目的什么地方修改了這個值,他就會在全局起作用。而且像這種跨線程訪問是否存在異常,我們通常都會去檢查。如果項目中其他人修改了這個屬性,那么我們的方案就失敗了。
第二個就是所謂的使用delegate和invoke來從其他線程中控制控件信息,但是我不這么認為,感覺這種方法的要點在於Control.Invoke Method,而感覺使用delegate和invoke會會誤導別人,好像使用一個delegate然后invoke就好了,其實delegate只不過是起輔助作用而已,真正起作用的還是Control.Invoke Method,看下面的code:
public partial class Form1 : Form { private delegate void FlushClient();//代理 public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { Thread thread = new Thread(CrossThreadFlush); thread.IsBackground=true; thread.Start(); } private void CrossThreadFlush() { //將代理綁定到方法 FlushClient fc = new FlushClient(ThreadFuntion); this.BeginInvoke(fc); //重點其實是在這里,前面定義的delegate什么的都是為了讓創建這個Control的線程去調用這個ThreadFuntion 方法
} private void ThreadFuntion() { while (true) { this.textBox1.Text = DateTime.Now.ToString(); Thread.Sleep(1000); } } }
說到Control.Invoke Method,就不得不提下Control.InvokeRequired Property,借助下面的code來看下這個property:
f (this.InvokeRequired) { this.BeginInvoke(new MethodInvoker(LoadGlobalImage)); return; }
c#中禁止跨線程直接訪問控件,InvokeRequired是為了解決這個問題而產生的當一個控件的InvokeRequired屬性值為真時,說明有一個創建它以外的線程想訪問它。此時它將會在內部調用new MethodInvoker(LoadGlobalImage)來完成下面的步驟,這個做法保證了控件的安全,你可以這樣理解,有人想找你借錢,他可以直接在你的錢包中拿,這樣太不安全,因此必須讓別人先要告訴你,你再從自己的錢包把錢拿出來借給別人,這樣就安全了。
這樣就可以很好的理解上面那個winform程序中 this.BeginInvoke(fc);這行code了,這個執行后其實也就是主線程在調用fc中綁定的方法ThreadFuntion()了,這種方式其實相當於把這個新開的線程“注入”到了主控制線程中,它取得了主線程的控制。只要這個線程不返回,那么主線程將永遠都無法響應。就算新開的線程中不使用無限循環,使可以返回了。這種方式的使用多線程也失去了它本來的意義。
下面就要用到InvokeRequired這個propety
public partial class Form1 : Form { private delegate void FlushClient();//代理 public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { Thread thread = new Thread(CrossThreadFlush); thread.IsBackground = true; thread.Start(); } private void CrossThreadFlush() { while (true) { //將sleep和無限循環放在等待異步的外面 Thread.Sleep(1000); ThreadFunction(); } } private void ThreadFunction() { if (this.textBox1.InvokeRequired)//等待異步 { FlushClient fc = new FlushClient(ThreadFunction); this.Invoke(fc);//通過代理調用刷新方法 } else { this.textBox1.Text = DateTime.Now.ToString(); } } }
運行上述代碼,我們可以看到問題已經被解決了,通過等待異步,我們就不會總是持有主線程的控制,這樣就可以在不發生跨線程調用異常的情況下完成多線程對winform多線程控件的控制了。