winform程序中使用線程的必要性:
單線程操作在執行耗時任務時會造成界面假死,帶來非常差勁的用戶體驗,有時候甚至會影響到正常的業務執行,使用多線程做相關操作實屬不得已之舉。
那么在編寫程序之前必須要明白的一個點就是窗體的UI的操作只能通過UI線程來執行,其他線程如果要去執行窗體中的控件值修改或者其它【任何和窗體線程相關的操作】,就會報異常,所有人都知道的。為了適應這一特性,於是就有了這樣的寫法:
private void button1_Click(object sender, EventArgs e)
{
this.BeginInvoke(new Action(delegate()
{
this.button1.Text = "test";
}));
}
意思很明顯就是在本窗體中執行如下代碼,說白了就是讓括號中的代碼在UI線程中執行,如果只是執行一個很簡單的任務不會有任何問題,因為時間夠快,給人的感覺好像窗體並沒有因為這樣寫就假死。現在把代碼改成如下這樣:
private void button1_Click(object sender, EventArgs e)
{
this.BeginInvoke(new Action(delegate()
{
for (int i = 0; i < 100; i++)
{
this.button1.Text = i.ToString();
Thread.Sleep(1000);
}
}));
}
預期的執行結果應該是,每隔一秒按鈕上邊的文本就會自加1直到100,但結果並不是這樣,當點擊按鈕之后,窗體會進入假死狀態,點擊不會有任何響應。這篇文章就是要解決這樣的問題。主要也是做一個簡單的總結,備用。
要處理這樣的問題最簡單粗暴的方式是這樣直接忽略掉其他線程不可以執行UI。代碼非常簡單,只需要在界面初始化中添加如下代碼就可以,
public Form1() { InitializeComponent(); CheckForIllegalCrossThreadCalls = false;//時候捕獲對錯誤線程的調用... 忽略掉自然就可以在其他線程中去訪問窗體線程了。 } private void button1_Click(object sender, EventArgs e) { Thread t = new Thread(() => { this.button1.Text = "測試"; }); t.Start(); }
這種方式確實已經做到可以在不同線程中去操作窗體線程,但並沒有什么卵用,遇到上邊的那種情況每隔一秒讓按鈕的數字自增一,依然無法做到。
請回頭看標顏色的那句話。
然后再看看Invoke,BeginInvoke到底是什么東西:
直接F12找到簽名對應的解釋
// // 摘要: // 在創建控件的基礎句柄所在線程上異步執行指定委托。 // // 參數: // method: // 對不帶參數的方法的委托。 // // 返回結果: // 一個表示 System.Windows.Forms.Control.BeginInvoke(System.Delegate) 操作的結果的 System.IAsyncResult。 // // 異常: // System.InvalidOperationException: // 找不到適當的窗口句柄。 [EditorBrowsable(EditorBrowsableState.Advanced)] public IAsyncResult BeginInvoke(Delegate method);
// // 摘要: // 在擁有此控件的基礎窗口句柄的線程上執行指定的委托。 // // 參數: // method: // 包含要在控件的線程上下文中調用的方法的委托。 // // 返回結果: // 正在被調用的委托的返回值,或者如果委托沒有返回值,則為 null。 public object Invoke(Delegate method);
關鍵字: 擁有此控件的基礎窗口句柄的線程上執行執行的委托。同步異步的區別
this.BeginInvoke(new Action(delegate()
{
for (int i = 0; i < 100; i++)
{
this.button1.Text = i.ToString();
Thread.Sleep(1000);
}
}));
那么在這里的意思就是在窗體線程中執行button.text=i.tostring,然后讓窗體線程休眠1000毫秒,窗體休眠了,自然而然就不會對你的操作做出響應,不管是不是異步都是在窗體線程中執行的,顯而易見問題是出在這里的,那么既然知道了問題所在。解決的辦法也非常簡單,那就是,
讓所有和窗體操作無關的任務不要在窗體線程中執行,所有和窗體相關操作的動作全部放到窗體線程中去執行,大家各行其道,問題就自然解決了。剛剛的按鈕文本每秒加1,就可以用下邊的這種方式來寫:
private void button1_Click(object sender, EventArgs e) { Thread t = new Thread(() => { for (int i = 0; i < 100; i++) { Thread.Sleep(1000); this.button1.Invoke(new Action(delegate() { this.button1.Text = i.ToString(); })); } }); t.Start(); }
沒錯,就是這樣,新開一個線程,讓所有的操作都在線程中執行,其中如果有涉及到對窗體的操作轉會到窗體線程執行對應操作,或者像這樣
public Form1() { InitializeComponent(); CheckForIllegalCrossThreadCalls = false;//忽略其他線程執行UI的錯誤 } private void button1_Click(object sender, EventArgs e) { Thread t = new Thread(() => { for (int i = 0; i < 100; i++) { Thread.Sleep(1000); this.button1.Text = i.ToString(); } }); t.Start(); }
這種方式明顯是有點取巧,而且在一定情況下會造成窗體閃爍,可能會不穩定,比如多個線程同時執行一個按鈕的text顯示,但至少這種方式寫起來沒那么麻煩。至於如何取舍就具體問題具體分析處理了。
