使用多執行緒使軟體介面具有較好的響應性 (轉)

amyz發表於2007-08-14
使用多執行緒使軟體介面具有較好的響應性 (轉)[@more@]

 介面的響應特性是判斷一款軟體的非常重要的方面。一般來說,不管你軟體功能做得有多麼奇妙,如果軟體有一點點當機的感覺都會讓感到很討厭,甚至懷疑你軟體裡是否藏有更大的問題。

 要提高介面的響應特性,最好的辦法莫過於使用多執行緒,並把呈現介面的執行緒獨立出來。以前只有使用C++才能實現的多執行緒功能,現在在下,所有的語言(包括VB)都可以使用了。不過,使用多執行緒比使用單一執行緒要麻煩得多,比如執行緒之間的同步問題,做得不好很容易出錯,而有的時候這種錯誤要開發人員花上幾個星期的時間才能找到。在 Form軟體中使用多執行緒更是有一些限制。

 下面我們就把在Windows Form軟體中使用多執行緒要注意的問題給大家做一個介紹。

先,什麼樣的操作需要考慮使用多執行緒?總的一條就是,負責與使用者互動的執行緒(以下簡稱為UI執行緒)應該保持順暢,當UI執行緒的可能引起阻塞時間超過30毫秒時(比如訪問CD-ROM等速度超慢的外設、進行呼叫等等)就應該考慮使用多執行緒。為什麼是30毫秒?30毫秒的概念是人眼可以察覺到的一個遲滯,大約等同於電影裡的一幀停留的時間,最長不要超過100毫秒。

 第二,最方便和簡單的多執行緒是使用執行緒池。透過執行緒池裡的執行緒執行程式碼的最簡便方法則是使用非同步委託呼叫。注意委託呼叫通常是同步完成的,請使用BeginInvoke方法,這樣就可以把要呼叫的方法排隊到執行緒池裡等候處理,而的流程會立刻返回到呼叫方(此處是UI執行緒),而呼叫方因此不會出現阻塞。

 看看下面的例子我們就發現要使用執行緒池非同步程式碼也並非十分複雜,這裡我們利用System.Windows.Forms.MethodInvoker委託進行非同步呼叫。注意MethodInvoker委託不接受方法引數,如果需要向非同步執行的方法傳遞引數,請使用其他委託,或者需要自己定義。

private void StartSomeWorkFromUIThread () {
  // 我們要做的工作相對UI執行緒而言臺慢了,用下面的方法非同步進行處理
  MethodInvoker mi = new MethodInvoker(RunsOnWorkerThread);//這是入口方法
  mi.BeginInvoke(null, null); // 這樣就不會阻塞
}

// 緩慢的工作在此方法內進行處理,使用執行緒池裡的執行緒
private void RunsOnWorkerThread() {
  DoSomethingSlow();
}

 歸納上述方法,對UI執行緒而言實際上就是:1、發出呼叫,2、立刻返回,具體執行過程不理了,這樣UI執行緒就不會被阻塞。這種方法很重要,下面我們會深入介紹。除了上面的方法,還有其他使用執行緒池的方法,當然如果你高興也可以自己建立執行緒。

 第三,在Windows Form中使用多執行緒的,最重要的一條注意事項是,除了建立的執行緒以外,絕對不要在任何其他執行緒裡面呼叫控制元件的成員(只有極個別情況例外),也就是說控制元件屬於建立它的執行緒,不能從其他執行緒裡面訪問。這一條適用於所有從System.Windows.Forms.Control派生的控制元件(因此可以說是幾乎所有控制元件),包括Form控制元件本身也是。舉一反三,我們很容易得出這樣的結論,控制元件的子控制元件必須由建立控制元件的執行緒來建立,比如一個表單上的按鈕,比如由建立表單的執行緒來建立,因此,一個視窗中的所有控制元件實際上都活在同一個執行緒之中。在實際時,大多數的軟體的做法都是讓同一執行緒負責全部的控制元件,這就是我們所說的UI執行緒。看下面的例子:

// 這是由UI執行緒定義的Label控制元件
private Label lblStatus;
....
// 以下方法不在UI執行緒上執行
private void RunsOnWorkerThread() {
  DoSomethingSlow();
  lblStatus.Text = "Finished!";  // 這是錯的
}

 我們要特別提醒大家,很多人剛開始的時候都會使用以上的方法來訪問不在同一個執行緒裡的控制元件(包括筆者本人),而且在1.0版.Net 框架上似乎沒有發現問題,但是這根本就是錯的,更糟糕的是,程式設計師在這裡不會得到任何錯誤提示,一開始就上當受騙,之後會莫明其妙地發現其他錯誤,這就是Windows Form多執行緒程式設計的痛苦所在。筆者試過花很多時間來De自己寫的Splash視窗突然消失的問題,結果還是失敗了:筆者在軟體的引導過程中,用另外一個執行緒裡建立了一個Splash視窗來顯示歡迎資訊,然後嘗試把主執行緒裡引導的狀態直接寫入到Splash視窗上的控制元件中,開始還OK,可是過一會Splash視窗就莫明其妙消失了。

 理解了這一點,我們應該留意到,有時候即使沒有用System.Threading.Thread來顯式建立一個執行緒,我們也可能因為使用了非同步委託的BeginInvoke方法來隱式建立了執行緒(從執行緒池裡),在這種執行緒裡也同樣不能呼叫UI執行緒所建立的控制元件的成員。

 第四,由於上述限制,我們可能會感到很不方便,的確,當我們利用一個新建立的執行緒來執行某些花時間的運算時,怎樣知道運算進度如何並透過UI反映給使用者呢?解決方法很多!比如熟悉多執行緒程式設計的使用者很快會想到,我們採用一些低階的同步方法,工作者執行緒把狀態儲存到一個同步中,讓UI執行緒輪詢(Polling)該物件並反饋給使用者就可以了。不過,這還是挺麻煩的,實際上不用這樣做,Control類(及其派生類)物件有一個Invoke方法很特別,這是少數幾個不受執行緒限制的成員之一。我們前面說到,絕對不要在任何其他執行緒裡面呼叫非本執行緒建立的控制元件的成員時,也說了“只有極個別情況例外”,這個Invoke方法就是極個別情況之一----Invoke方法可以從任何執行緒裡面呼叫。下面我們來講解Invoke方法。

 Invoke方法的引數很簡單,一個委託,一個參數列(可選),而Invoke方法的主要功能就是幫助你在UI執行緒(即建立控制元件的執行緒)上呼叫委託所指定的方法。Invoke方法首先檢查發出呼叫的執行緒(即當前執行緒)是不是UI執行緒,如果是,直接執行委託指向的方法,如果不是,它將切換到UI執行緒,然後執行委託指向的方法。不管當前執行緒是不是UI執行緒,Invoke都阻塞直到委託指向的方法執行完畢,然後切換回發出呼叫的執行緒(如果需要的話),返回。注意,使用Invoke方法時,UI執行緒不能處於阻塞狀態。以下MSDN裡關於Invoke方法的說明:

  “控制元件上有四種方法可以地從任何執行緒進行呼叫:Invoke、BeginInvoke、EndInvoke 和 CreateGraphics。對於所有其他方法呼叫,則應使用呼叫 (invoke) 方法之一封送對控制元件的執行緒的呼叫。
  委託可以是 EventHandler 的例項,在此情況下,傳送方引數將包含此控制元件,而事件引數將包含 EventArgs.Empty。委託還可以是 MethodInvoker 的例項或採用 void 引數列表的其他任何委託。呼叫 EventHandler 或 MethodInvoker 委託比呼叫其他型別的委託速度更快。”

 好了,說完Invoke,順便說說BeginInvoke,毫無疑問這是Invoke的非同步版本(Invoke是同步完成的),不過大家不要和上面的System.Windows.Forms.MethodInvoker委託中的BeginInvoke混淆,兩者都是利用不同執行緒來完成工作,但是控制元件的BeginInvoke方法總是使用UI執行緒,而其他的非同步委託呼叫方法則是利用執行緒池裡的執行緒。相對Invoke而言,使用BeginInvoke稍稍麻煩一點,但還是那句話,非同步比同步效果好,儘管複雜些。比如同步方法可能出現這樣一種死鎖情況:工作者執行緒透過Invoke同步呼叫UI執行緒裡的方法時會阻塞,而萬一UI執行緒正在等待工作者執行緒做某件事時怎麼辦?因此,能夠使用非同步方法時應儘量使用非同步方法。

  下面我們利用所學到的知識來改寫上面那個簡單的例子:

// 這是由UI執行緒定義的Label控制元件
private Label lblStatus;
....
// 以下方法不在UI執行緒上執行
private void RunsOnWorkerThread() {
  DoSomethingSlow();
  // Do UI update on UI thread
  [] pList = { this, System.EventArgs.Empty };
  lblStatus.BeginInvoke(
  new System.EventHandler(UpdateUI), pList);
}
....


// 切換回UI執行緒執行的入口
private void UpdateUI(object o, System.EventArgs e) {
  //現在沒問題了,使用Invoke使得執行緒總是回到UI執行緒,所以我們可以放心大膽地呼叫控制元件的成員了
  lblStatus.Text = "Finished!";
}

  第五,關於多執行緒程式設計還要考慮執行緒之間的同步問題、死鎖和爭用條件,有關這類問題的文章很多,我們就不贅述了。



來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-956452/,如需轉載,請註明出處,否則將追究法律責任。

相關文章