国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > .NET > 正文

在 Visual Basic .NET 中實現后臺進程

2024-07-10 13:00:56
字體:
來源:轉載
供稿:網友
rockford lhotka
magenic technologies
2002年10月1日
從 msdn code center 下載 vbbackground.exe 示例文件(英文)。(請注意,在示例文件中,程序員的注釋使用的是英文,本文中將其譯為中文是為了便于讀者進行理解。)

摘要:rocky lhotka 建議并實現了一個結構化架構示例,該架構可用于充當輔助線程和 ui 線程之間的媒介,從而簡化編寫多線程輔助代碼和 ui 以對其進行控制的過程。該架構包括可下載的代碼示例,可以根據您的應用需要進行調整。

使用多線程,可以使應用程序同時執行多項任務。使用多線程,可以讓一個線程運行用戶界面,讓另一個線程進行復雜運算或在后臺操作。由于 microsoft® visual basic® .net 支持多線程,因此我們很容易獲得此功能。

但多線程也有其不足之處。當應用程序使用多個線程時,我們總會遇到這樣的問題:多個線程同時嘗試與相同的數據或資源進行交互。出現這種情況時,問題就會變得非常復雜并且難以調試。

更糟糕的是,多線程代碼通常在最初開發期間似乎運行正常,但在生產過程中則會因為出現意外的情況(多個線程同時與相同的數據或資源進行交互)而導致失敗。這樣就增大了多線程編程的危險性!

由于設計和調試多線程應用程序非常困難,因此 microsoft 在 com 中創建了“單線程單元”(sta) 概念。visual basic 6 代碼始終在 sta 中運行,因而代碼只需考慮單線程即可。這樣即可徹底避免共享數據或資源所帶來的問題,但是同時也意味著,我們必須采取嚴格的措施才能利用多線程的優勢。

.net 中不會出現 sta 中的這種常見問題。所有 .net 代碼都在允許多線程操作的 appdomain 中運行。這意味著 visual basic .net 代碼也在 appdomain 中運行,因此可以使用多線程操作。顯然,任何時候進行此操作都必須小心編寫代碼,以避免線程之間的沖突。

要避免線程之間發生沖突,最簡單的方法就是確保多個線程永遠不會與相同的數據或資源進行交互。盡管不太可能,但是對于任何多線程應用程序來說,應該在設計時盡量避免使用或盡量少使用共享數據或共享資源。

這樣不僅能簡化編碼和調試過程,還能提高性能。要解決線程之間的沖突,必須使用能夠在某個線程完成操作之前阻止或暫停其他線程的同步技術。阻止線程也就是使線程處于空閑狀態,不進行任何操作,因此會降低性能。

取消按鈕和狀態顯示
在應用程序中使用多線程的原因有多種,但最常見的原因是我們一方面需要執行一個長時間運行的任務,另一方面又希望某些或所有用戶界面對用戶來說始終處于響應狀態。

至少我們應該使 cancel(取消)按鈕始終保持響應狀態,使用戶能夠通過它告訴系統,他們希望終止長時間運行的任務。

在 visual basic 6 中,我們嘗試使用 doevents、計時器控件和許多其他方法進行該操作。visual basic .net 中的操作則簡單得多,因為我們可以使用多線程。而且,只要我們小心謹慎,就可以完成此操作且不會使代碼或調試復雜化。

要在多線程環境中成功實現 cancel(取消)按鈕,關鍵是要記住 cancel(按鈕)的作用只是“請求”取消任務。由后臺任務決定何時停止。

如果我們實現一個能夠直接停止后臺進程的 cancel(取消)按鈕,則可能會在執行某些敏感性操作的過程中將其停止,或者在后臺進程關閉重要資源(例如,文件處理程序或數據庫連接)之前將其停止。而這有可能導致嚴重后果,引起死機、應用程序行為不穩定或應用程序完全崩潰。

因此,cancel(取消)按鈕的作用應該只是請求停止后臺任務。后臺任務可以檢查某一時間點上是否存在取消操作的請求。如果檢測到取消操作的請求,后臺線程則可以釋放所有資源,停止所有重要操作并正常終止。

雖然請求取消操作非常重要,但是我們更希望能夠通過 ui 為用戶顯示后臺進程的狀態信息。狀態信息可以是文本格式的消息,也可以是完成任務的百分比,或者同時顯示兩種消息。

要在 visual basic .net 中實現 cancel(取消)按鈕或狀態顯示,我們所面對的最復雜的問題在于 windows 窗體庫不是對于線程并不安全。這意味著只有創建窗體的線程可以與該窗體或其控件進行交互。其他線程均不能安全地與該窗體或其控件進行交互。

但是,我們卻無法避免編寫多線程與給定窗體進行交互的代碼。因此,運行時可能會產生不可預知的后果,甚至可能會導致應用程序崩潰。

這要求我們在編碼時必須小心謹慎,還要確保只有我們的 ui 線程與 ui 進行交互。為此,我們可以建立一個簡單的架構,管理后臺輔助線程和 ui 線程之間的交互。如果能夠實現,則可以使 ui 代碼和長時間運行的任務的代碼都相對清楚地了解到我們正在使用多線程。

線程和對象
如果要創建一個后臺進程并使其可以使用它自己的數據在它自己的線程上運行,最簡單的方法是創建專門用于該后臺進程的對象。雖然不一定能實現,但它是一個積極的目標,因為它能夠大大簡化多線程應用程序的創建過程。

如果后臺線程在其自身的對象中運行,則后臺線程可以使用該對象的實例變量(在類中聲明的變量),而無須擔心這些變量會被其他線程使用。例如,請考慮下面的類:

public class worker
private minner as integer
private mouter as integer

public sub new(byval innersize as integer, byval outersize as integer)
minner = innersize
mouter = outersize
end sub

public sub work()
dim innerindex as integer
dim outerindex as integer

dim value as double

for outerindex = 0 to mouter
for innerindex = 0 to minner
' do some cool calculation here
value = math.sqrt(cdbl(innerindex - outerindex))
next
next
end sub

end class
這個類適合在后臺線程中運行,并且可以使用以下代碼啟動:

dim myworker as new worker(10000000, 10)
dim backthread as new thread(addressof myworker.work)
backthread.start()
worker 類中的實例變量可以存放其數據。后臺線程可以安全地使用這些變量(minner 和 mouter),還可以確保其他線程不會同時訪問這些變量。

我們可以用其中包含的 constructor 方法使用任何起始數據初始化該對象。實際啟動后臺線程之前,我們的主應用程序代碼會創建此對象的實例,并使用后臺線程將要操作的數據對其進行初始化。

后臺線程將獲取對象的 work 方法的地址,然后開始啟動。此線程將立即在對象內部運行代碼,并使用該對象的專用數據。

由于對象是自包含的,因此我們可以創建多個對象,每個對象在其自身的線程上運行并且對象之間相對獨立。

但是,此實現方案并不理想。ui 無法獲得后臺進程的狀態。我們也未實現任何機制,使 ui 能夠請求終止后臺進程。

要解決以上兩個問題,后臺線程與 ui 線程之間需要以某種方式進行交互。這種交互方式非常復雜,因此最好能夠以某種方式將交互放到一個類中,這樣 ui 和輔助代碼就不必為細節而擔心。

體系結構
我們可以創建使 ui 和輔助代碼無需進行線程交互操作的體系結構。實際上我們可以實現此目標,還能實現一個能夠通過某種方式實現復雜代碼的架構,可以用來管理或控制后臺線程及其 ui 交互。

我們先來討論體系結構,然后再討論如何設計和實現代碼。從本文的相關鏈接可以下載此代碼以及說明如何使用此代碼的示例應用程序。

通常情況下,應用程序中首先會啟動一個單一線程,來打開用戶界面。我們將其命名為“ui 線程”以便于理解。“ui 線程”是許多應用程序中的唯一線程,因此它要處理 ui 并完成所有操作。

但是,現在我們創建一個“輔助線程”進行某些后臺操作,讓 ui 線程集中處理用戶界面。這樣即使輔助線程繁忙,ui 線程也可以對用戶保持響應狀態。

我們在 ui 線程和輔助線程之間插入一層代碼,使其充當 ui 和輔助代碼之間的接口。此代碼實質上是一個“控制器”,用來管理和控制輔助線程及其與 ui 之間的交互。



圖 1:ui 線程、控制器和輔助線程

控制器包含的代碼可以安全地啟動輔助線程,將任何狀態消息從輔助線程中轉給 ui 線程,以及將任何取消請求從 ui 線程中轉回輔助線程。ui 代碼和輔助代碼不能直接交互,它們通常要通過控制器的代碼進行交互。

但是輔助線程被激活“之前”和“之后”的時間段除外,這時 ui 代碼可以與 worker 對象進行交互。啟動輔助線程之前,ui 可以創建并初始化 worker 對象。終止輔助線程之后,ui 可以從 worker 對象中檢索任何值。從 ui 的角度看,將形成以下事件流:

創建 worker 對象。
初始化 worker 對象。
調用 controller 以啟動輔助線程。
worker 對象將通過 controller 將狀態信息發送給 ui。
ui 可以通過 controller 將取消請求發送給 worker 對象。
worker 對象在完成操作后通過 controller 通知 ui。
值可以直接從 worker 對象中檢索。
除了在輔助線程處于激活狀態時 ui 代碼無法與 worker 對象直接交互的限制外,對 ui 沒有特殊的編碼要求。即使正在運行后臺操作,ui 也會對用戶保持激活和響應狀態。

從 worker 對象的角度看,將形成以下事件流:

ui 代碼創建 worker 對象。
ui 代碼使用所需的數據初始化 worker 對象。
controller 創建后臺線程并調用 worker 對象的方法。
worker 對象運行輔助代碼。
worker 對象將狀態信息傳遞給 controller,以便 controller 將狀態信息中轉給 ui。
worker 對象適時檢查是否存在取消請求,如果存在,則停止運行。
worker 對象完成后,告訴 controller 已完成,以便 controller 將該信息中轉給 ui。
現在輔助線程已終止,因此 ui 可以與 worker 對象直接交互。
由于輔助代碼只與 controller 交互,因此我們不必擔心輔助線程會意外地與 ui 組件交互(這無疑會使應用程序不穩定)。現在,輔助代碼依靠 controller 與 ui 線程進行正確通信,因此各項操作都很安全。

這意味著,只要處理好 worker 對象中的實例變量,就無需處理輔助代碼中的任何線程問題。

使用圖表通常能夠很好地了解不同組件(尤其是不同線程上的組件)之間的交互。microsoft® visio® 支持創建 uml(通用建模語言)圖表,對理解很有幫助。

以下是說明 ui、worker 對象和 controller 之間事件流的 uml 序列圖表。此圖表假設不存在任何取消操作請求。worker 和 controller 對象下面重疊在垂直線上的垂直活動欄突出了輔助線程上運行的代碼。其他所有代碼都在 ui 線程上運行。



圖 2:說明進程流的序列圖表

使用 uml 活動圖表也可以查看事件流。這種圖表形式的著重點在于任務而不是對象,因此其中顯示了發生的一系列步驟以及各步驟之間的流程。我們很容易看出 ui 代碼如何停留在左側的線程中,而 worker 對象如何在右側的線程上工作。worker 對象在其他線程中運行之前和運行之后可以直接由 ui 使用,以便初始化值,然后再檢索結果。



圖 3:顯示進程流的活動圖表

使用這樣的圖表可以幫助我們找出后臺線程處于激活狀態時,ui 與輔助線程(或反過來)無意中進行直接交互的位置。任何這樣的交互都需要額外地進行編碼,以避免出現可能使應用程序不穩定的錯誤。理想狀態下,這種交互通過 controller 組件來實現,我們可以在其中包含所有編碼,使交互安全進行。

下圖說明了 ui 發出取消請求時的事件序列。



圖 4:顯示取消請求的序列圖表

請注意,取消請求從 ui 發送到 controller,然后 worker 線程與 controller 進行核實,確定是否發生了取消請求。ui 和 controller 都不會強制輔助代碼終止,而是允許輔助代碼自己正常安全地終止。

架構設計
要實現我們討論的行為,顯然需要實現 controller 類。為了使此架構能夠在多數方案中應用,我們還會定義一些正式接口,可以由 controller 在與 ui(或客戶端)和輔助線程交互時使用。

通過為客戶端和輔助線程定義正式接口,我們可以在不同的情況下使用相同的 controller 對象,還可以根據需要使用不同的 ui 要求和不同的 worker 對象。

下面的 uml 類圖表顯示了 controller 類以及 iclient 和 iworker 接口。它還顯示了 icontroller 接口,輔助代碼將通過它與 controller 對象交互。



圖 5:controller 和相關接口的類圖表

iclient 接口定義的方法將由 controller 對象調用,用于向客戶端 ui 通報 worker 的開始時間、結束時間和任何中間狀態消息。它還包含一個指示輔助代碼失敗的方法。

多數情況下,我們可以將這些方法作為由 controller 對象發出而由 ui 處理的事件來實現。但是,從輔助線程發出事件然后由 ui 線程正確處理并非易事,因而我們將其作為一組方法來進行實現。

使控制器代碼(在輔助代碼上運行)調用 ui 中的這些方法并由 ui 線程進行處理,這樣相對要簡單得多。

同樣,iworker 接口定義了由 controller 對象調用的、使其可以與輔助代碼交互的方法。使用 initialize 方法可以為輔助代碼提供對 controller 對象的引用,而使用 start 方法可以啟動后臺線程上的操作。

由于線程的工作方式,start 方法無法包含任何參數。啟動新線程時,必須將不接受任何參數的方法的地址傳遞給線程。

請注意,iworker 接口中不存在 cancel 或 stop 方法。我們不能強制輔助代碼停止,同時也沒有這個必要;但是輔助代碼可以使用 icontroller 接口詢問 controller 對象是否存在取消請求。

icontroller 接口定義了輔助代碼可以在 controller 對象上調用的方法。它允許輔助代碼檢查 running 標志。如果存在取消請求,running 標志即為 false。它還允許輔助代碼在工作完成或無法完成時告訴 controller,并允許使用狀態消息和完成百分比值(0 到 100 之間的 integer)更新 controller。

最后我們定義了 controller 對象。該對象中包含一些可以被 ui 代碼調用的方法。其中包括 start 方法,該方法可以通過為 controller 對象提供對 worker 對象的引用來啟動后臺操作。還包括 cancel 方法,該方法用于請求取消操作。ui 也可以檢查 running 屬性,查看是否存在取消請求;還可以檢查 percent 屬性,查看任務完成的百分比。

controller 類中包含的 constructor 方法接受 iclient 作為參數,還允許 ui 為 controller 提供對窗體(用于處理 worker 中的顯示消息)的引用。

為了實現一系列動畫點來顯示線程的活動,我們將創建一個簡單 windows 窗體控件,該控件使用計時器以更改一系列 picturebox 控件中的顏色。

實現方案
我們將在 class library(類庫)項目中實現此架構,使其可用于需要運行后臺進程的應用程序。

打開 visual studio .net,然后創建一個名為 background 的新 class library(類庫)應用程序。由于此庫將包含 windows 窗體控件和窗體,因此需要使用 add references(添加引用)對話框引用 system.windows.forms.dll 和 system.windows.drawing.dll。此外,我們可以使用項目的屬性對話框在這些項目范圍內導入命名空間,如圖 6 所示。



圖 6:使用項目屬性添加項目范圍內的命名空間 imports

此操作完成后,就可以開始編碼了。讓我們先從創建接口開始。

定義接口
在名為 iclient 的項目中添加一個類,并用以下代碼替換其代碼:

public interface iclient
sub start(byval controller as controller)
sub display(byval text as string)
sub failed(byval e as exception)
sub completed(byval cancelled as boolean)
end interface

然后添加名為 iworker 的類,并用以下代碼替換其代碼:

public interface iworker
sub initialize(byval controller as icontroller)
sub start()
end interface

最后添加名為 icontroller 的類,代碼如下:

public interface icontroller
readonly property running() as boolean
sub display(byval text as string)
sub setpercent(byval percent as integer)
sub failed(byval e as exception)
sub completed(byval cancelled as boolean)
end interface

至此,我們已定義了本文前面所述的所有類圖表中的接口。現在可以實現 controller 類了。

controller 類
現在,我們可以實現架構的核心,controller 類。此類中包含的代碼可用于啟動輔助線程,以及在輔助線程完成之前充當 ui 線程和輔助線程之間的媒介。

在名為 controller 的項目中添加一個新類。首先添加 imports,并聲明一些變量:

imports system.threading

public class controller
implements icontroller

private mworker as iworker
private mclient as form
private mrunning as boolean
private mpercent as integer
然后需要聲明一些委托。委托是方法的正式指針,而且方法的委托必須具有與方法本身相同的方法簽名(參數類型等)。

委托的用途很廣。在我們的示例中,委托非常重要,因為委托使我們可以讓一個線程調用窗體上的方法,使其在該窗體的 ui 線程上運行。正如 iclient 所定義的那樣,要在窗體上調用的三個方法都需要委托:

' 此委托簽名與 iclient.completed
' 中的簽名相匹配,并用于安全地
' 調用 ui 線程上的方法
private delegate sub completeddelegate(byval cancelled as boolean)

' 此委托簽名與 iclient.display
' 中的簽名相匹配,并用于安全地
' 調用 ui 線程上的方法
private delegate sub displaydelegate(byval text as string)

' 此委托簽名與 iclient.failed
' 中的簽名相匹配,并用于安全地
' 調用 ui 線程上的方法
private delegate sub faileddelegate(byval e as exception)

iclient 還定義了 start 方法,但是該方法可以從 ui 線程調用,因此不需要委托。

下面編寫將從 ui 線程調用的代碼。代碼中包括 constructor 方法、start 和 cancel 方法以及 percent 屬性。我將這些內容放入 region 中,便于大家清楚地了解它們是從 ui 線程調用的。

#region " 從 ui 線程調用的代碼 "

' 使用客戶端初始化 controller
public sub new(byval client as iclient)
mclient = ctype(client, form)
end sub

' 此方法由 ui 調用,因此在
' ui 線程上運行。此處我們將
' 啟動輔助線程
public sub start(optional byval worker as iworker = nothing)
' 如果輔助線程已經啟動,將產生錯誤
if mrunning then
throw new exception("background process already running")
end if

mrunning = true

' 存儲對輔助對象的引用,并
' 初始化輔助對象,使其包含
' 對 controller 的引用
mworker = worker
mworker.initialize(me)

' 創建后臺線程
' 以進行后臺操作
dim backthread as new thread(addressof mworker.start)

' 開始后臺工作
backthread.start()

' 告訴客戶端后臺工作已開始
ctype(mclient, iclient).start(me)
end sub

' 此代碼由 ui 調用,因此在 ui
' 線程上運行。它只設置了請求
' 取消的標志
public sub cancel()
mrunning = false
end sub

' 返回完成百分比值,并且
' 只被 ui 線程調用
public readonly property percent() as integer
get
return mpercent
end get
end property

#end region

此處唯一比較特殊的代碼位于 start 方法中,我們可以在該方法中創建輔助線程然后啟動該線程:

dim backthread as new thread(addressof mworker.start)

backthread.start()

要創建線程,需要在 worker 對象的 iworker 接口上傳遞 start 方法的地址。然后,只需調用線程對象的 start 方法即可開始操作。此時我們要特別注意,ui 不應直接與 worker 交互,worker 也不應直接與 ui 交互。

請注意,cancel 方法只設置一個標志,表明我們不希望繼續運行。輔助代碼應定期查看此標志,以確定是否應該停止運行。

現在,我們可以實現 worker 對象運行時將由輔助線程調用的代碼。此代碼比較有趣,因為它必須將 display 和 completed 從輔助線程中轉至 ui 線程,同時還要在 ui 線程上完成此操作。

要完成此操作,我們可以使用 form 對象的 invoke 方法。此方法接受窗體應該調用的方法的委托指針,以及包含該方法的參數的 object 類型數組。

invoke 方法不直接調用窗體上的方法,而是請求窗體返回并使用窗體的 ui 線程調用該方法。此操作可通過向窗體發送 windows 消息在后臺完成。這說明窗體獲得這些方法調用的方式與從操作系統中獲得 click 或 keypress 事件的方式基本相同。

通常,這些細節不會影響大局。結果由 invoke 方法觸發一個進程,通過該進程窗體將終止其 ui 線程上運行的方法,這就是我們要實現的目標。

再次重申,此代碼位于 region 內,目的是為了明確它將在輔助線程上調用:

#region " 從輔助線程調用的代碼 "

' 從輔助線程調用,以更新顯示
' 這將觸發對包含狀態文本的 ui 的
' 方法調用 - 該調用是在 ui 線程上
' 進行的
private sub display(byval text as string) _
implements icontroller.display

dim disp as new displaydelegate( _
addressof ctype(mclient, iclient).display)
dim ar() as object = {text}

' 調用 ui 線程上的客戶端窗體
' 以更新顯示
mclient.begininvoke(disp, ar)

end sub

' 從輔助線程調用,以表明出現故障
' 這將觸發對包含異常對象的 ui 的
' 方法調用 - 該調用是在 ui 線程上
' 進行的
private sub failed(byval e as exception) _
implements icontroller.failed

dim disp as new faileddelegate(_
addressof ctype(mclient, iclient).failed)
dim ar() as object = {e}

' 在 ui 線程上調用客戶端窗體
' 以表明出現故障
mclient.invoke(disp, ar)

end sub

' 從輔助線程上調用,以指出完成的百分比
' 值將轉到 controller,由 ui 在需要時讀取
private sub setpercent(byval percent as integer) _
implements icontroller.setpercent

mpercent = percent

end sub

' 從輔助線程調用,以表明已完成
' 我們還傳遞參數,以表明是否真正完成,
' 以及是否取消在 ui 線程上進行的對 ui
' 的調用
private sub completed(byval cancelled as boolean) _
implements icontroller.completed

mrunning = false
dim comp as new completeddelegate( _
addressof ctype(mclient, iclient).completed)
dim ar() as object = {cancelled}

' 調用 ui 線程上的客戶端窗體
' 以表明已完成
mclient.invoke(comp, ar)

end sub

' 表明是否仍在運行或是否已請求取消
' 這將在輔助線程上進行調用,因此
' 輔助代碼可以查看它是否應該正常
' 退出
private readonly property running() as boolean _
implements icontroller.running
get
return mrunning
end get
end property

#end region

failed 和 completed 方法利用窗體的 invoke 方法。例如,failed 方法可以執行以下操作:

dim disp as new faileddelegate(_
addressof ctype(mclient, iclient).failed)
dim ar() as object = {e}

' 調用 ui 線程上的客戶端窗體
' 以表明出現故障
mclient.invoke(disp, ar)

首先創建一個委托,從 iclient 接口指向客戶端窗體的 failed 方法。然后聲明包含向方法傳遞參數值的 object 類型數組。最后調用客戶端窗體的 invoke 方法,將委托指針和參數數組傳遞給窗體。

窗體將在 ui 線程(窗體在這里可以安全運行以更新顯示)上使用這些參數調用此方法。

整個進程是同步進行的,即對窗體進行調用時輔助線程將停止。盡管可以在顯示錯誤消息或完成消息時停止輔助線程,但我們并不希望顯示每個小狀態時都停止輔助線程。

為了避免顯示狀態時停止輔助線程,display 方法將使用 begininvoke,而不使用 invoke。begininvoke 使窗體上的方法調用異步進行,這樣輔助線程可以一直保持運行狀態,不需要等待窗體上的顯示方法完成:

dim disp as new displaydelegate( _
addressof ctype(mclient, iclient).display)
dim ar() as object = {text}

' 調用 ui 線程上的客戶端窗體
' 以更新顯示
mclient.begininvoke(disp, ar)

以這種方式使用 begininvoke 可以防止輔助線程停止,使輔助線程具有盡可能高的性能。

activitybar 控件
最后,我們來創建顯示動畫點的 activitybar 控件。

在名為 activitybar 的項目中添加一個用戶控件。

將該控件的寬度調整為約 110,高度調整為約 20。可以通過拖動邊界進行調整,也可以通過在 properties(屬性)窗口中設置 size 屬性進行調整。

其余的操作將通過代碼完成。要創建一系列在顯示時不停閃爍的動畫“燈”,可以使用帶有 timer 控件的一系列 picturebox 控件。每次 timer 控件關閉時,我們將使下一個 picturebox 呈綠色顯示,并將已經呈綠色顯示的 picturebox 更改為窗體的背景色。

將 windows forms(windows 窗體)選項卡中的 timer 控件放入窗體中,然后將其名稱更改為 tmanim。同時將 interval 屬性設置為 300,以獲得較好的動畫速度。

順便說一句,components(組件)選項卡中有一個不同的 timer 控件。它是一個多線程計時器。也就是說,該計時器將在后臺線程中引發 elapsed 事件,而不是象 windows 窗體計時器那樣在 ui 線程上引發 elapsed 事件。建立 ui 時這種方法通常會產生相反的效果,因為 elapsed 事件中的代碼顯然不能直接與我們的 ui 進行交互。

現在,在控件中添加以下代碼:

private mboxes as new arraylist()
private mcount as integer

private sub activitybar_load(byval sender as system.object, _
byval e as system.eventargs) handles mybase.load

dim index as integer

if mboxes.count = 0 then
for index = 0 to 6
mboxes.add(createbox(index))
next
end if
mcount = 0

end sub

private function createbox(byval index as integer) as picturebox
dim box as new picturebox()

with box
setposition(box, index)
.borderstyle = borderstyle.fixed3d
.parent = me
.visible = true
end with
return box
end function

private sub graydisplay()
dim index as integer

for index = 0 to 6
ctype(mboxes(index), picturebox).backcolor = me.backcolor
next
end sub

private sub setposition(byval box as picturebox, byval index as integer)
dim left as integer = cint(me.width / 2 - 7 * 14 / 2)
dim top as integer = cint(me.height / 2 - 5)

with box
.height = 10
.width = 10
.top = top
.left = left + index * 14
end with
end sub

private sub tmanim_tick(byval sender as system.object, _
byval e as system.eventargs) handles tmanim.tick

ctype(mboxes((mcount + 1) mod 7), picturebox).backcolor = _
color.lightgreen
ctype(mboxes(mcount mod 7), picturebox).backcolor = me.backcolor

mcount += 1
if mcount > 6 then mcount = 0
end sub

public sub start()
ctype(mboxes(0), picturebox).backcolor = color.lightgreen
tmanim.enabled = true
end sub

public sub [stop]()
tmanim.enabled = false
graydisplay()
end sub

private sub activitybar_resize(byval sender as object, _
byval e as system.eventargs) handles mybase.resize

dim index as integer

for index = 0 to mboxes.count - 1
setposition(ctype(mboxes(index), picturebox), index)
next
end sub

窗體的 load 事件創建 picturebox 控件并將它們放入數組,這樣便于我們在它們之間循環。timer 控件的 tick 事件循環顯示,使各個控件依次呈綠色。

所有操作由 start 方法開始,由 stop 事件結束。由于 stop 是一個保留字,因此把這個方法名放在方括號內:[stop]。stop 方法不僅可以停止計時器,還可以灰顯所有框,告訴用戶這些框中當前沒有活動。

創建 worker 類
本文前面已簡單介紹了 worker 類。因為我們已經定義了 iworker 接口,所以可以增強該類,以利用我們創建的 controller。

首先創建 background.dll 文件。此步驟很重要,因為如果不完成此步驟,activitybar 控件將無法在我們建立測試窗體時顯示在工具箱上。

在解決方案中添加名為 bgtest 的 windows forms application(windows 窗體應用程序)。在 solution explorer(解決方案資源瀏覽器)中用右鍵單擊該項目并選擇相應的菜單項,將該程序設置為啟動項目。

然后使用 add references(添加引用)對話框中的 projects(項目)選項卡,添加對 background 項目的引用。

現在,在名為 worker 的項目中添加一個類。其中部分代碼與前面所述的代碼相同,但還包含一些不同的代碼,用以實現 iworker 接口(此處突出顯示的部分):

imports background

public class worker
implements iworker

private mcontroller as icontroller
private minner as integer
private mouter as integer

public sub new(byval innersize as integer, byval outersize as integer)
minner = innersize
mouter = outersize
end sub

' 由 controller 調用,以便獲取
' controller 的引用
private sub init(byval controller as icontroller) _
implements iworker.initialize

mcontroller = controller

end sub

private sub work() implements iworker.start
dim innerindex as integer
dim outerindex as integer

dim value as double

try
for outerindex = 0 to mouter
if mcontroller.running then
mcontroller.display("outer loop " & outerindex & " starting")
mcontroller.setpercent(cint(outerindex / mouter * 100))

else
' 它們請求取消
mcontroller.completed(true)
exit sub
end if

for innerindex = 0 to minner
' 此處進行一些有意思的計算
value = math.sqrt(cdbl(innerindex - outerindex))
next
next
mcontroller.setpercent(100)
mcontroller.completed(false)

catch e as exception
mcontroller.failed(e)

end try
end sub
end class

我們添加了能夠實現 iworker.initialize 的 init 方法。controller 將調用此方法,因此以后我們可以引用 controller 對象。

我們還將 work 方法更改為 private,只是為了實現 iworker.start 方法。此方法將在輔助線程上運行。

我們增強了 work 方法,使其可以使用 try..catch 塊。這樣我們可以使用 controller 上的 failed 方法捕捉任何錯誤并將其返回給 ui。

假設代碼正在運行,我們調用 controller 對象的 display 和 setpercent 方法,使它們隨著代碼的運行更新其狀態和完成的百分比。

我們還定期檢查 controller 對象的 running 屬性,查看是否存在取消請求。如果存在取消請求,則停止進程,并指示由于取消請求而停止操作。

創建顯示的窗體
最后,我們可以創建窗體,將其用于啟動或取消后臺進程。該窗體還將顯示活動和狀態信息。

打開 form1 的設計器并添加兩個按鈕(btnstart 和 btnrequestcancel)、兩個標簽(label1 和 label2)、一個 progressbar (progressbar1) 和一個 activitybar (activitybar1),如圖 7 所示。



圖 7:form1 控件的布局

該窗體需要實現 iclient,以便 controller 對象與之交互:

imports background

public class form1
inherits system.windows.forms.form

implements iclient

該窗體還需要 controller 對象和一個標志,用以跟蹤后臺操作是處于活動狀態還是處于完成狀態。

private mcontroller as new controller(me)
private mactive as boolean

然后,我們可以添加方法,以實現由 iclient 定義的接口。建議將這些方法放在 region 中,以表示它們實現的是輔助接口:

#region " iclient "

private sub taskstarted(byval controller as controller) _
implements iclient.start

mactive = true
label1.text = "starting"
label2.text = "0%"
progressbar1.value = 0
activitybar1.start()

end sub

private sub taskstatus(byval text as string) _
implements iclient.display

label1.text = text
label2.text = cstr(mcontroller.percent) & "%"
progressbar1.value = mcontroller.percent

end sub

private sub taskfailed(byval e as exception) _
implements iclient.failed

activitybar1.stop()
label1.text = e.message
msgbox(e.tostring)
mactive = false

end sub

private sub taskcompleted(byval cancelled as boolean) _
implements iclient.completed

label1.text = "completed"
label2.text = cstr(mcontroller.percent) & "%"
progressbar1.value = mcontroller.percent
activitybar1.stop()
mactive = false

end sub

#end region

請注意,這一段代碼中的所有內容均與線程無關,其中的每一部分代碼都可以在我們得知后臺操作的狀態時做出相應的響應。每次響應后,我們都會更新顯示以表明進程的狀態和完成百分比(以文字的形式或通過 progressbar 顯示),并啟動和停止 activitybar 控件。

mactive 標志非常重要。如果用戶在輔助線程處于活動狀態時關閉窗體,應用程序可能會掛起或變得不穩定。要防止出現這種情況,我們可以打斷窗體的 closing 事件并取消關閉嘗試(如果后臺進程處于活動狀態)。

private sub form1_closing(byval sender as object, _
byval e as system.componentmodel.canceleventargs) _
handles mybase.closing

e.cancel = mactive

end sub

我們還可以選擇在這種情況下初始化取消操作,但是這取決于特定的應用程序要求。

其余的代碼都是為了實現按鈕的 click 事件。

private sub btnstart_click(byval sender as system.object, _
byval e as system.eventargs) handles btnstart.click

mcontroller.start(new worker(2000000, 100))

end sub

private sub btnstop_click(byval sender as system.object, _
byval e as system.eventargs) handles btnstop.click

label1.text = "cancelling ..."
mcontroller.cancel()

end sub

start(開始)按鈕只調用 controller 對象的 start 方法,并將 worker 對象的實例傳遞給它。

您可能需要調整用于初始化 worker 對象的值,以便在您的計算機上獲得所需的結果。這些特定的值提供了雙處理器 p3/450 計算機上的一個良好示例。顯然,這只是用于測試目的。真正的 worker 對象將實現更有意義、運行時間更長的進程。

cancel(取消)按鈕將調用 controller 對象的 cancel 方法,同時還會更新顯示,以表明已請求取消。請記住,這只是一個取消“請求”,在輔助線程真正停止運行之前可能需要等待一些時間。最好能夠為用戶提供即時反饋,至少應讓用戶知道系統已經注意到用戶的單擊按鈕操作。

現在,我們可以運行應用程序了。單擊 start(開始)按鈕時,worker 就應該開始運行,而且顯示的內容會在運行時更新。您可以將窗體移動到屏幕上的任意位置,也可以與其交互,因為 ui 線程本質上還處于空閑狀態,可以隨時與您交互。

同時,輔助線程在后臺進行大量復雜的工作,并定期將狀態更新信息發送給 ui 線程以進行顯示。

小結
多線程是一個功能強大的工具,我們可以在每次需要執行長時間運行的任務時使用該工具。我們可以用它運行輔助代碼,而無需綁定用戶界面。但同時要注意,多線程操作非常復雜,要正確操作并不容易,而且調試起來也比較困難。

盡管不一定能夠實現,但我們還是應該盡量為每個輔助線程提供一組它可以操作的獨立數據。要達到這個目的,最簡單的方法就是為每個線程創建一個對象,對象中包含該線程可以操作的數據以及完成工作所需的代碼。

通過實現結構化的架構,使之充當輔助線程和 ui 線程之間的媒介,我們可以大大簡化編寫多線程代碼和 ui 以對其進行控制的過程。本文就介紹了這樣一個架構,您可以根據需要使用或進行調整,以滿足特定的應用需要。

最大的網站源碼資源下載站,

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 麦盖提县| 沂水县| 黄龙县| 乌兰浩特市| 江孜县| 漯河市| 自治县| 京山县| 东山县| 安塞县| 台州市| 广东省| 沅陵县| 宝丰县| 中牟县| 安庆市| 泰兴市| 嫩江县| 积石山| 永兴县| 玛多县| 长宁区| 防城港市| 张北县| 韶关市| 麟游县| 长治市| 进贤县| 临高县| 醴陵市| 贞丰县| 富顺县| 桂阳县| 通州市| 新余市| 周至县| 定边县| 正蓝旗| 鄂尔多斯市| 观塘区| 柳林县|