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

2022-01-15 11:10:31 字數 3961 閱讀 2943

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

要提高介面的響應特性,最好的辦法莫過於使用多執行緒,並把呈現介面的執行緒獨立出來。以前只有使用c++才能實現的多

執行緒功能,現在在.net框架下,所有的語言(包括vb)都可以使用了。不過,使用多執行緒比使用單一執行緒要麻煩得多,比如執行緒之間的同步問題,做得不好很

容易出錯,而有的時候這種錯誤要開發人員花上幾個星期的時間才能找到。在windows form軟體中使用多執行緒更是有一些限制。

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

首先,什麼樣的操作需要考慮使用多執行緒?總的一條就是,負責與使用者互動的執行緒(以下簡稱為ui執行緒)應該保持順暢,當ui執行緒呼叫的api可能引起阻塞時

間超過30毫秒時(比如訪問cd-rom等速度超慢的外設、進行遠端呼叫等等)就應該考慮使用多執行緒。為什麼是30毫秒?30毫秒的概念是人眼可以察覺到

的乙個遲滯,大約等同於電影裡的一幀停留的時間,最長不要超過100毫秒。

第二,最方便和簡單的多執行緒是使用執行緒池。通過執行緒池裡的執行緒執行**的最簡便方法則是使用非同步委託呼叫。注意委託

呼叫通常是同步完成的,請使用begininvoke方法,這樣就可以把要呼叫的方法排隊到執行緒池裡等候處理,而程式的流程會立刻返回到呼叫方(此處是

ui執行緒),而呼叫方因此不會出現阻塞。

看看下面的例子我們就發現要使用執行緒池非同步執行**也並非十分複雜,這裡我們利用

system.windows.forms.methodinvoker委託進行非同步呼叫。注意methodinvoker委託不接受方法引數,如果需要

向非同步執行的方法傳遞引數,請使用其他委託,或者需要自己定義。

private void startsomeworkfromuithread ()

// 緩慢的工作在此方法內進行處理,使用執行緒池裡的執行緒

private void runsonworkerthread()

歸納上述方法,對ui執行緒而言實際上就是:1、發出呼叫,2、立刻返回,具體執行過程不理了,這樣ui執行緒就不會被

阻塞。這種方法很重要,下面我們會深入介紹。除了上面的方法,還有其他使用執行緒池的方法,當然如果你高興也可以自己建立執行緒。

第三,在windows

form中使用多執行緒的,最重要的一條注意事項是,除了建立控制項的執行緒以外,絕對不要在任何其他執行緒裡面呼叫控制項的成員(只有極個別情況例外),也就是說

控制項屬於建立它的執行緒,不能從其他執行緒裡面訪問。這一條適用於所有從system.windows.forms.control派生的控制項(因此可以說是

幾乎所有控制項),包括form控制項本身也是。舉一反三,我們很容易得出這樣的結論,控制項的子控制項必須由建立控制項的執行緒來建立,比如乙個表單上的按鈕,比如

由建立表單的執行緒來建立,因此,乙個視窗中的所有控制項實際上都活在同乙個執行緒之中。在實際程式設計時,大多數的軟體的做法都是讓同一執行緒負責全部的控制項,這就

是我們所說的ui執行緒。看下面的例子:

// 這是由ui執行緒定義的label控制項

private label lblstatus;

....

// 以下方法不在ui執行緒上執行

private void runsonworkerthread()

我們要特別提醒大家,很多人剛開始的時候都會使用以上的方法來訪問不在同乙個執行緒裡的控制項(包括筆者本人),而且在

1.0版.net

框架上似乎沒有發現問題,但是這根本就是錯的,更糟糕的是,程式設計師在這裡不會得到任何錯誤提示,一開始就上當受騙,之後會莫明其妙地發現其他錯誤,這就是

windows

form多執行緒程式設計的痛苦所在。筆者試過花很多時間來debug自己寫的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() ;

lblstatus.begininvoke(

new system.eventhandler(updateui), plist);

} ....

// 切換回ui執行緒執行的入口

private void updateui(object o, system.eventargs e)

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

python 多執行緒threading的使用

一 執行緒建立方法 1.普通建立 import threading defrun name for i in range 3 print name if name main t1 threading.thread target run,args t1 t2 threading.thread targ...

python多執行緒threading的使用

一 執行緒建立方法 1.普通建立 import threadingdef run name for i in range 3 print name if name main t1 threading.thread target run,args t1 t2 threading.thread targ...

Python多執行緒threading的使用

一.threading的引數傳遞,引數之後的 不能少,此處的 是用來區分此引數作為元組 包含多個引數 來傳遞的,而不是單個引數傳遞 coding utf 8 import threading import time def b arg time.sleep 2 print i m b print i...