原始鏈結:http://www.microsoft.com/taiwan/msdn/library/2002/Nov-2002/vbnet09272002.htm

作者:Rockford Lhotka
Magenic Technologies

2002 年 10 月 1 日

從 MSDN Code Center 下載 VBbackground.exe 範例檔案 (英文)。

(請注意:範例程式碼中的註解均為英文,此文章中所顯示的中文化註解,僅供參考)


摘要: Rocky Lhotka 向您推薦並實作結構化架構範例,作為背景工作執行緒與 UI 執行緒之間的橋樑,以簡化撰寫多執行緒背景工作程式碼以及控制該程式碼的 UI 的流程。 (列印共 27 頁)。

多執行緒處理可讓應用程式同時執行一個以上的工作。使用多執行緒處理時,一個執行緒會在使用者介面上執行,而其他執行緒會在背景進行密集計算或處理。Microsoft® Visual Basic® .NET 支援多執行緒處理,所以我們可以輕鬆運用這項功能。

不過,多執行緒處理也有缺點。若應用程式使用兩個以上的執行緒,且數個執行緒同時與相同的資料或資源互動,則可能會出現問題。在這種情況下,問題會變得非常複雜,而且不容易進行偵錯。

更糟的是,在開發初期時,多執行緒程式碼表面上通常運作良好,只有在最後階段,才會因為事先未察覺數個執行緒會與相同資料或資源互動,而導致失敗。這種缺點讓多執行緒程式開發十分危險!

基於設計和偵錯多執行緒應用程式的困難性,Microsoft 在 COM 中建立了單一執行緒 Apartment (STA) 概念。Visual Basic 6 程式碼永遠根據 STA 執行,因此我們的程式碼永遠只需要注意單一的執行緒。如此可避免發生共用資料或資源的問題,但也使我們無法利用多執行緒處理所帶來的優勢,而必須向外尋求協助。

.NET 則不受 STA 的限制。所有 .NET 程式碼都在 AppDomain 中執行,而 AppDomain 可支援多執行緒處理。這表示 Visual Basic .NET 程式碼也在 AppDomain 中執行,因此可以利用多執行緒的優勢。很明顯地,每當我們使用多執行緒處理時,都必須格外小心,以避免發生執行緒衝突。

最容易避免執行緒衝突的方法就是確保這些執行緒絕對不會與相同資料或資源互動,雖然有時無法做到,但是凡在設計任何多執行緒應用程式時,都應該把避免或減少使用共用資料或資源當作首要目標。

如此不僅能簡化編碼和偵錯作業,還可增進效能。若要解決執行緒衝突,我們必須使用同步技術,同步技術會封鎖執行緒或暫時停止執行緒,直到其他執行緒的動作完成為止。封鎖執行緒表示此執行緒閒置中、不執行任何工作,因此會降低效能。

取消按鈕和狀態顯示
應用程式使用多執行緒處理的理由很多,而主要的理由是當我們執行一項執行時間長的工作時,我們希望部分或所有使用者介面仍然能夠回應使用者。

至少,我們通常會要求 [取消] 按鈕能夠保持回應,好讓使用者有機會表示要結束長時間執行的工作。

在 Visual Basic 6 中,我們能使用 DoEvents、計時器控制項以及許多其他解決方法。而在 Visual Basic .NET 中,由於可使用多執行緒處理,因此解決方法更容易,只要小心處理就可以,且不會使程式碼或偵錯作業的複雜性提高。

在多執行緒環境下實作 [取消] 按鈕的成功秘訣在於:記住 [取消] 按鈕只是要求要取消工作。背景工作本身有權決定停止工作的適當時機。

如果我們實作的 [取消] 按鈕真的可以直接停止背景處理,我們可能會打斷某些特殊的作業,或是當重要資源 (例如,檔案控制代碼或資料庫連接) 正要關閉時,停止其工作。上述情況可能會造成重大錯誤、發生死結、不穩定的應用程式行為或應用程式完全損毀。

相反地,[取消] 按鈕應該只是要求停止背景工作。而背景工作可以檢查要求取消工作的時機是否恰當。當背景工作偵測到取消的要求時,背景執行緒可以釋放任何資源、停止任何重要的活動,並從容地結束工作。

要求取消作業的功能固然重要,但是我們可能還希望 UI 可以為使用者顯示背景處理的狀態資訊。狀態資訊的顯示方式可能是文字訊息、完成百分比或兩者兼具。

在 Visual Basic .NET 中實作 [取消] 按鈕或狀態顯示時,所要面臨的主要難題在於 Windows Form 程式庫並非安全執行緒。這就表示只有建立表單的執行緒,才能與該表單或表單的控制項互動。其他執行緒則無法與表單或其控制項進行安全互動。

但是即使有再多困難,也阻止不了我們撰寫多執行緒與特定表單互動的程式碼。除非在 Runtime 中發生無法預期的結果和潛在的應用程式損毀...

因此我們必須小心翼翼的撰寫程式碼,以確保只有我們的 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
        ' 在此處進行計算
        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 和背景工作程式碼間的介面。此程式碼基本上屬於 Controller 元件,可管理及控制背景工作執行緒和它與 UI 的互動。

arrow
arrow
    全站熱搜

    戮克 發表在 痞客邦 留言(0) 人氣()