鷹翔宇空

          學(xué)習(xí)和生活

          BlogJava 首頁 新隨筆 聯(lián)系 聚合 管理
            110 Posts :: 141 Stories :: 315 Comments :: 1 Trackbacks

          原文引自:http://www.microsoft.com/china/msdn/library/architecture/architecture/architecturetopic/SCArchDeGuide/Chapter1Introduction.mspx

           

          第 6 章 — 使用多線程

          發(fā)布日期: 08/20/2004 | 更新日期: 08/20/2004
          pponline

          智能客戶端體系結(jié)構(gòu)與設(shè)計(jì)指南

          David Hill、Brenton Webster、Edward A. Jezierski、Srinath Vasireddy 和 Mohammad Al-Sabt,Microsoft Corporation;Blaine Wastell,Ascentium Corporation;Jonathan Rasmusson 和 Paul Gale,ThoughtWorks;以及 Paul Slater,Wadeware LLC

          相關(guān)鏈接

          Microsoft? patterns & practices 庫: http://www.microsoft.com/resources/practices/default.mspx

          .NET 的應(yīng)用程序體系結(jié)構(gòu):設(shè)計(jì)應(yīng)用程序和服務(wù)http://msdn.microsoft.com/library/en-us/dnbda/html/distapp.asp

          摘要:本章討論與智能客戶端應(yīng)用程序中多線程的使用有關(guān)的問題。為了最大限度地提高智能客戶端應(yīng)用程序的響應(yīng)能力,需要仔細(xì)考慮如何和何時(shí)使用多線程。線程可以大大提高應(yīng)用程序的可用性和性能,但是當(dāng)您確定它們將如何與用戶界面交互時(shí),需要對其進(jìn)行非常仔細(xì)的考慮。

          *
          本頁內(nèi)容
          .NET Framework 中的多線程處理 .NET Framework 中的多線程處理
          何時(shí)使用多線程 何時(shí)使用多線程
          創(chuàng)建和使用線程 創(chuàng)建和使用線程
          使用任務(wù)處理 UI 線程和其他線程之間的交互 使用任務(wù)處理 UI 線程和其他線程之間的交互
          小結(jié) 小結(jié)

          線程是基本執(zhí)行單元。單線程執(zhí)行一系列應(yīng)用程序指令,并且在應(yīng)用程序中從頭到尾都經(jīng)由單一的邏輯路徑。所有的應(yīng)用程序都至少有一個(gè)線程,但是您可以將它們設(shè)計(jì)成使用多線程,并且每個(gè)線程執(zhí)行一個(gè)單獨(dú)的邏輯。在應(yīng)用程序中使用多線程,可以將冗長的或非常耗時(shí)的任務(wù)放在后臺(tái)處理。即使在只有單處理器的計(jì)算機(jī)上,使用多線程也可以非常顯著地提高應(yīng)用程序的響應(yīng)能力和可用性。

          使用多線程來開發(fā)應(yīng)用程序可能非常復(fù)雜,特別是當(dāng)您沒有仔細(xì)考慮鎖定和同步問題時(shí)。當(dāng)開發(fā)智能客戶端應(yīng)用程序時(shí),需要仔細(xì)地評估應(yīng)該在何處使用多線程和如何使用多線程,這樣就可以獲得最大的好處,而無需創(chuàng)建不必要的復(fù)雜并難于調(diào)試的應(yīng)用程序。

          本章研究對于開發(fā)多線程智能客戶端應(yīng)用程序最重要的一些概念。它介紹了一些值得推薦的在智能客戶端應(yīng)用程序中使用多線程的方法,并且描述了如何實(shí)現(xiàn)這些功能。

          .NET Framework 中的多線程處理

          所有的 .NET Framework 應(yīng)用程序都是使用單線程創(chuàng)建的,單線程用于執(zhí)行該應(yīng)用程序。在智能客戶端應(yīng)用程序中,這樣的線程創(chuàng)建并管理用戶界面 (UI),因而稱為 UI 線程。

          可以將 UI 線程用于所有的處理,其中包括 Web 服務(wù)調(diào)用、遠(yuǎn)程對象調(diào)用和數(shù)據(jù)庫調(diào)用。然而,以這種方式使用 UI 線程通常并不是 一個(gè)好主意。在大多數(shù)情況下,您不能預(yù)測調(diào)用 Web 服務(wù)、遠(yuǎn)程對象或數(shù)據(jù)庫會(huì)持續(xù)多久,而且在 UI 線程等待響應(yīng)時(shí),您可能會(huì)導(dǎo)致 UI 凍結(jié)。

          通過創(chuàng)建附加線程,應(yīng)用程序可以在不使用 UI 線程的情況下執(zhí)行額外的處理。當(dāng)應(yīng)用程序調(diào)用 Web 服務(wù)時(shí),可以使用多線程來防止 UI 凍結(jié)或并行執(zhí)行某些本地任務(wù),以整體提高應(yīng)用程序的效率。在大多數(shù)情況下,您應(yīng)該堅(jiān)持在單獨(dú)的線程上執(zhí)行任何與 UI 無關(guān)的任務(wù)。

          同步和異步調(diào)用之間的選擇

          應(yīng)用程序既可以進(jìn)行同步調(diào)用,也可以進(jìn)行異步調(diào)用。同步 調(diào)用在繼續(xù)之前等待響應(yīng)或返回值。如果不允許調(diào)用繼續(xù),就說調(diào)用被阻塞 了。

          異步非阻塞 調(diào)用不等待響應(yīng)。異步調(diào)用是通過使用單獨(dú)的線程執(zhí)行的。原始線程啟動(dòng)異步調(diào)用,異步調(diào)用使用另一個(gè)線程執(zhí)行請求,而與此同時(shí)原始的線程繼續(xù)處理。

          對于智能客戶端應(yīng)用程序,將 UI 線程中的同步調(diào)用減到最少非常重要。在設(shè)計(jì)智能客戶端應(yīng)用程序時(shí),應(yīng)該考慮應(yīng)用程序?qū)⑦M(jìn)行的每個(gè)調(diào)用,并確定同步調(diào)用是否會(huì)對應(yīng)用程序的響應(yīng)和性能產(chǎn)生負(fù)面影響。

          僅在下列情況下,使用 UI 線程中的同步調(diào)用:

          ?

          執(zhí)行操縱 UI 的操作。

          ?

          執(zhí)行不會(huì)產(chǎn)生導(dǎo)致 UI 凍結(jié)的風(fēng)險(xiǎn)的小的、定義完善的操作。

          在下列情況下,使用 UI 線程中的異步調(diào)用:

          ?

          執(zhí)行不影響 UI 的后臺(tái)操作。

          ?

          調(diào)用位于網(wǎng)絡(luò)的其他系統(tǒng)或資源。

          ?

          執(zhí)行可能花費(fèi)很長時(shí)間才能完成的操作。

          前臺(tái)線程和后臺(tái)線程之間的選擇

          .NET Framework 中的所有線程都被指定為前臺(tái)線程或后臺(tái)線程。這兩種線程唯一的區(qū)別是 — 后臺(tái)線程不會(huì)阻止進(jìn)程終止。在屬于一個(gè)進(jìn)程的所有前臺(tái)線程終止之后,公共語言運(yùn)行庫 (CLR) 就會(huì)結(jié)束進(jìn)程,從而終止仍在運(yùn)行的任何后臺(tái)線程。

          在默認(rèn)情況下,通過創(chuàng)建并啟動(dòng)新的 Thread 對象生成的所有線程都是前臺(tái)線程,而從非托管代碼進(jìn)入托管執(zhí)行環(huán)境中的所有線程都標(biāo)記為后臺(tái)線程。然而,通過修改 Thread.IsBackground 屬性,可以指定一個(gè)線程是前臺(tái)線程還是后臺(tái)線程。通過將 Thread.IsBackground 設(shè)置為 true,可以將一個(gè)線程指定為后臺(tái)線程;通過將 Thread.IsBackground 設(shè)置為 false,可以將一個(gè)線程指定為前臺(tái)線程。

          有關(guān) Thread 對象的詳細(xì)信息,請參閱本章后面的“使用 Thread 類”部分

          在大多數(shù)應(yīng)用程序中,您會(huì)選擇將不同的線程設(shè)置成前臺(tái)線程或后臺(tái)線程。通常,應(yīng)該將被動(dòng)偵聽活動(dòng)的線程設(shè)置為后臺(tái)線程,而將負(fù)責(zé)發(fā)送數(shù)據(jù)的線程設(shè)置為前臺(tái)線程,這樣,在所有的數(shù)據(jù)發(fā)送完畢之前該線程不會(huì)被終止。

          只有在確認(rèn)線程被系統(tǒng)隨意終止沒有不利影響時(shí),才應(yīng)該使用后臺(tái)線程。如果線程正在執(zhí)行必須完成的敏感操作或事務(wù)操作,或者需要控制關(guān)閉線程的方式以便釋放重要資源,則使用前臺(tái)線程。

          處理鎖定和同步

          有時(shí)在構(gòu)建應(yīng)用程序時(shí),創(chuàng)建的多個(gè)線程都需要同時(shí)使用關(guān)鍵資源(例如數(shù)據(jù)或應(yīng)用程序組件)。如果不仔細(xì),一個(gè)線程就可能更改另一個(gè)線程正在使用的資源。其結(jié)果可能就是該資源處于一種不確定的狀態(tài)并且呈現(xiàn)為不可用。這稱為 爭用情形。在沒有仔細(xì)考慮共享資源使用的情況下使用多線程的其他不利影響包括:死鎖、線程饑餓和線程關(guān)系問題。

          為了防止這些影響,當(dāng)從兩個(gè)或多個(gè)線程訪問一個(gè)資源時(shí),需要使用鎖定和同步技術(shù)來協(xié)調(diào)這些嘗試訪問此資源的線程。

          使用鎖定和同步來管理線程訪問共享資源是一項(xiàng)復(fù)雜的任務(wù),只要有可能,就應(yīng)該通過在線程之間傳送數(shù)據(jù)而不是提供對單個(gè)實(shí)例的共享訪問來避免這樣做。

          假如不能排除線程之間的資源共享,則應(yīng)該:

          ?

          使用 Microsoft Visual C# 中的 lock 語句和 Microsoft Visual Basic .NET 中的 SyncLock 語句來創(chuàng)建臨界區(qū),但要小心地從臨界區(qū)內(nèi)調(diào)用方法來防止死鎖。

          ?

          使用 Synchronized 方法獲得線程安全的 .NET 集合。

          ?

          使用 ThreadStatic 屬性創(chuàng)建逐線程成員。

          ?

          使用重新檢查 (double-check) 鎖或 Interlocked.CompareExchange 方法來防止不必要的鎖定。

          ?

          確保靜態(tài)聲明是線程安全的。

          有關(guān)鎖定和同步技術(shù)的詳細(xì)信息,請參閱 http://msdn.microsoft.com/library/en-us/cpgenref/html/cpconthreadingdesignguidelines.asp 上的 .NET Framework General Reference 中的“Threading Design Guidelines”。

          使用計(jì)時(shí)器

          在某些情況下,可能不需要使用單獨(dú)的線程。如果應(yīng)用程序需要定期執(zhí)行簡單的與 UI 有關(guān)的操作,則應(yīng)該考慮使用進(jìn)程計(jì)時(shí)器。有時(shí),在智能客戶端應(yīng)用程序中使用進(jìn)程計(jì)時(shí)器,以達(dá)到下列目:

          ?

          按計(jì)劃定期執(zhí)行操作。

          ?

          在使用圖形時(shí)保持一致的動(dòng)畫速度(而不管處理器的速度)。

          ?

          監(jiān)視服務(wù)器和其他的應(yīng)用程序以確認(rèn)它們在線并且正在運(yùn)行。

          .NET Framework 提供三種進(jìn)程計(jì)時(shí)器:

          ?

          System.Window.Forms.Timer

          ?

          System.Timers.Timer

          ?

          System.Threading.Timer

          如果想要在 Windows 窗體應(yīng)用程序中引發(fā)事件,System.Window.Forms.Timer 就非常有用。它經(jīng)過了專門的優(yōu)化以便與 Windows 窗體一起使用,并且必須用在 Windows 窗體中。它設(shè)計(jì)成能用于單線程環(huán)境,并且可以在 UI 線程上同步操作。這就意味著該計(jì)時(shí)器從來不會(huì)搶占應(yīng)用程序代碼的執(zhí)行(假定沒有調(diào)用 Application.DoEvents),并且對與 UI 交互是安全的。

          System.Timers.Timer 被設(shè)計(jì)并優(yōu)化成能用于多線程環(huán)境。與 System.Window.Forms.Timer 不同,此計(jì)時(shí)器調(diào)用從 CLR 線程池中獲得的輔助線程上的事件處理程序。在這種情況下,應(yīng)該確保事件處理程序不與 UI 交互。System.Timers.Timer 公開了可以模擬 System.Windows.Forms.Timer 中的行為的 SynchronizingObject 屬性,但是除非需要對事件的時(shí)間安排進(jìn)行更精確的控制,否則還是應(yīng)該改為使用 System.Windows.Forms.Timer

          System.Threading.Timer 是一個(gè)簡單的輕量級服務(wù)器端計(jì)時(shí)器。它并不是內(nèi)在線程安全的,并且使用起來比其他計(jì)時(shí)器更麻煩。此計(jì)時(shí)器通常不適合 Windows 窗體環(huán)境。

          表 6.1 列出了每個(gè)計(jì)時(shí)器的各種屬性。

          6.1 進(jìn)程計(jì)時(shí)器屬性

          屬性 System.Windows.Forms System.Timers System.Threading

          計(jì)時(shí)器事件運(yùn)行在什么線程中?

          UI 線程

          UI 線程或輔助線程

          輔助線程

          實(shí)例是線程安全的嗎?

          需要 Windows 窗體嗎?

          最初的計(jì)時(shí)器事件可以調(diào)度嗎?

          返回頁首返回頁首

          何時(shí)使用多線程

          在許多常見的情況下,可以使用多線程處理來顯著提高應(yīng)用程序的響應(yīng)能力和可用性。

          應(yīng)該慎重考慮使用多線程來:

          ?

          通過網(wǎng)絡(luò)(例如,與 Web 服務(wù)器、數(shù)據(jù)庫或遠(yuǎn)程對象)進(jìn)行通信。

          ?

          執(zhí)行需要較長時(shí)間因而可能導(dǎo)致 UI 凍結(jié)的本地操作。

          ?

          區(qū)分各種優(yōu)先級的任務(wù)。

          ?

          提高應(yīng)用程序啟動(dòng)和初始化的性能。

          非常詳細(xì)地分析這些使用情況是非常有用的。

          通過網(wǎng)絡(luò)進(jìn)行通信

          智能客戶端可以采用許多方式通過網(wǎng)絡(luò)進(jìn)行通信,其中包括:

          ?

          遠(yuǎn)程對象調(diào)用,例如,DCOM、RPC 或 .NET 遠(yuǎn)程處理

          ?

          基于消息的通信,例如,Web 服務(wù)調(diào)用和 HTTP 請求。

          ?

          分布式事務(wù)處理。

          許多因素決定了網(wǎng)絡(luò)服務(wù)對應(yīng)用程序請求的響應(yīng)速度,其中包括請求的性質(zhì)、網(wǎng)絡(luò)滯后時(shí)間、連接的可靠性和帶寬、單個(gè)服務(wù)或多個(gè)服務(wù)的繁忙程度。

          這種不可預(yù)測性可能會(huì)引起單線程應(yīng)用程序的響應(yīng)問題,而多線程處理常常是一種好的解決方案。應(yīng)該為網(wǎng)絡(luò)上的所有通信創(chuàng)建針對 UI 線程的單獨(dú)線程,然后在接收到響應(yīng)時(shí)將數(shù)據(jù)傳送回 UI 線程。

          為網(wǎng)絡(luò)通信創(chuàng)建單獨(dú)的線程并不總是必要的。如果應(yīng)用程序通過網(wǎng)絡(luò)進(jìn)行異步通信,例如使用 Microsoft Windows 消息隊(duì)列(也稱為 MSMQ),則在繼續(xù)執(zhí)行之前,它不會(huì)等待響應(yīng)。然而,即使在這種情況下,您仍然應(yīng)該使用單獨(dú)的線程來偵聽響應(yīng),并且在響應(yīng)到達(dá)時(shí)對其進(jìn)行處理。

          執(zhí)行本地操作

          即使在處理發(fā)生在本地的情況下,有些操作也可能花費(fèi)很長時(shí)間,足以對應(yīng)用程序的響應(yīng)產(chǎn)生負(fù)面影響。這樣的操作包括:

          ?

          圖像呈現(xiàn)。

          ?

          數(shù)據(jù)操縱。

          ?

          數(shù)據(jù)排序。

          ?

          搜索。

          不應(yīng)該在 UI 線程上執(zhí)行諸如此類的操作,因?yàn)檫@樣做會(huì)引起應(yīng)用程序中的性能問題。相反,應(yīng)該使用額外的線程來異步執(zhí)行這些操作,防止 UI 線程阻塞。

          在許多情況下,也應(yīng)該這樣設(shè)計(jì)應(yīng)用程序,讓它報(bào)告正在進(jìn)行的后臺(tái)操作的進(jìn)程和成功或失敗。可能還會(huì)考慮允許用戶取消后臺(tái)操作以提高可用性。

          區(qū)分各種優(yōu)先級的任務(wù)

          并不是應(yīng)用程序必須執(zhí)行的所有任務(wù)都具有相同的優(yōu)先級。一些任務(wù)對時(shí)間要求很急,而一些則不是。在其他的情況中,您或許會(huì)發(fā)現(xiàn)一個(gè)線程依賴于另一個(gè)線程上的處理結(jié)果。

          應(yīng)該創(chuàng)建不同優(yōu)先級的線程以反映正在執(zhí)行的任務(wù)的優(yōu)先級。例如,應(yīng)該使用高優(yōu)先級線程管理對時(shí)間要求很急的任務(wù),而使用低優(yōu)先級線程執(zhí)行被動(dòng)任務(wù)或者對時(shí)間不敏感的任務(wù)。

          應(yīng)用程序啟動(dòng)

          應(yīng)用程序在第一次運(yùn)行時(shí)常常必須執(zhí)行許多操作。例如,它可能需要初始化自己的狀態(tài),檢索或更新數(shù)據(jù),打開本地資源的連接。應(yīng)該考慮使用單獨(dú)的線程來初始化應(yīng)用程序,從而使得用戶能夠盡快地開始使用該應(yīng)用程序。使用單獨(dú)的線程進(jìn)行初始化可以增強(qiáng)應(yīng)用程序的響應(yīng)能力和可用性。

          如果確實(shí)在單獨(dú)的線程中執(zhí)行初始化,則應(yīng)該通過在初始化完成之后,更新 UI 菜單和工具欄按鈕的狀態(tài)來防止用戶啟動(dòng)依賴于初始化尚未完成的操作。還應(yīng)該提供清楚的反饋消息來通知用戶初始化的進(jìn)度。

          返回頁首返回頁首

          創(chuàng)建和使用線程

          在 .NET Framework 中有幾種方法可以創(chuàng)建和使用后臺(tái)線程。可以使用 ThreadPool 類訪問由 .NET Framework 管理的給定進(jìn)程的線程池,也可以使用 Thread 類顯式地創(chuàng)建和管理線程。另外,還可以選擇使用委托對象或者 Web 服務(wù)代理來使非 UI 線程上發(fā)生特定處理。本節(jié)將依次分析各種不同的方法,并推薦每種方法應(yīng)該在何時(shí)使用。

          使用 ThreadPool 類

          到現(xiàn)在為止,您可能會(huì)認(rèn)識(shí)到許多應(yīng)用程序都會(huì)從多線程處理中受益。然而,線程管理并不僅僅是每次想要執(zhí)行一個(gè)不同的任務(wù)就創(chuàng)建一個(gè)新線程的問題。有太多的線程可能會(huì)使得應(yīng)用程序耗費(fèi)一些不必要的系統(tǒng)資源,特別是,如果有大量短期運(yùn)行的操作,而所有這些操作都運(yùn)行在單獨(dú)線程上。另外,顯式地管理大量的線程可能是非常復(fù)雜的。

          線程池化技術(shù)通過給應(yīng)用程序提供由系統(tǒng)管理的輔助線程池解決了這些問題,從而使得您可以將注意力集中在應(yīng)用程序任務(wù)上而不是線程管理上。

          在需要時(shí),可以由應(yīng)用程序?qū)⒕€程添加到線程池中。當(dāng) CLR 最初啟動(dòng)時(shí),線程池沒有包含額外的線程。然而,當(dāng)應(yīng)用程序請求線程時(shí),它們就會(huì)被動(dòng)態(tài)創(chuàng)建并存儲(chǔ)在該池中。如果線程在一段時(shí)間內(nèi)沒有使用,這些線程就可能會(huì)被處置,因此線程池是根據(jù)應(yīng)用程序的要求縮小或擴(kuò)大的。

          每個(gè)進(jìn)程都創(chuàng)建一個(gè)線程池,因此,如果您在同一個(gè)進(jìn)程內(nèi)運(yùn)行幾個(gè)應(yīng)用程序域,則一個(gè)應(yīng)用程序域中的錯(cuò)誤可能會(huì)影響相同進(jìn)程內(nèi)的其他應(yīng)用程序域,因?yàn)樗鼈兌际褂孟嗤木€程池。

          線程池由兩種類型的線程組成:

          ?

          輔助線程。輔助線程是標(biāo)準(zhǔn)系統(tǒng)池的一部分。它們是由 .NET Framework 管理的標(biāo)準(zhǔn)線程,大多數(shù)功能都在它們上面執(zhí)行。

          ?

          完成端口線程.這種線程用于異步 I/O 操作(通過使用 IOCompletionPorts API)。

          ,如果應(yīng)用程序嘗試在沒有 IOCompletionPorts 功能的計(jì)算機(jī)上執(zhí)行 I/O 操作,它就會(huì)還原到使用輔助線程。

          對于每個(gè)計(jì)算機(jī)處理器,線程池都默認(rèn)包含 25 個(gè)線程。如果所有的 25 個(gè)線程都在被使用,則附加的請求將排入隊(duì)列,直到有一個(gè)線程變得可用為止。每個(gè)線程都使用默認(rèn)堆棧大小,并按默認(rèn)的優(yōu)先級運(yùn)行。

          下面代碼示例說明了線程池的使用。

          private void ThreadPoolExample() 
          { 
              WaitCallback callback = new WaitCallback( ThreadProc ); 
              ThreadPool.QueueUserWorkItem( callback ); 
          } 
          

          在前面的代碼中,首先創(chuàng)建一個(gè)委托來引用您想要在輔助線程中執(zhí)行的代碼。.NET Framework 定義了 WaitCallback 委托,該委托引用的方法接受一個(gè)對象參數(shù)并且沒有返回值。下面的方法實(shí)現(xiàn)您想要執(zhí)行的代碼。

          private void ThreadProc( Object stateInfo ) 
          { 
              // Do something on worker thread. 
          } 
          

          可以將單個(gè)對象參數(shù)傳遞給 ThreadProc 方法,方法是將其指定為 QueueUserWorkItem 方法調(diào)用中的第二個(gè)參數(shù)。在前面的示例中,沒有給 ThreadProc 方法傳遞參數(shù),因此 stateInfo 參數(shù)為空。

          在下面的情況下,使用 ThreadPool 類:

          ?

          有大量小的獨(dú)立任務(wù)要在后臺(tái)執(zhí)行。

          ?

          不需要對用來執(zhí)行任務(wù)的線程進(jìn)行精細(xì)控制。

          使用 Thread 類

          使用 Thread 類可以顯式管理線程。這包括 CLR 創(chuàng)建的線程和進(jìn)入托管環(huán)境執(zhí)行代碼的 CLR 以外創(chuàng)建的線程。CLR 監(jiān)視其進(jìn)程中曾經(jīng)在 .NET Framework 內(nèi)執(zhí)行代碼的所有線程,并且使用 Thread 類的實(shí)例來管理它們。

          只要有可能,就應(yīng)該使用 ThreadPool 類來創(chuàng)建線程。然而,在一些情況下,您還是需要?jiǎng)?chuàng)建并管理您自己的線程,而不是使用 ThreadPool 類。

          在下面的情況下,使用 Thread 對象:

          ?

          需要具有特定優(yōu)先級的任務(wù)。

          ?

          有可能運(yùn)行很長時(shí)間的任務(wù)(這樣可能阻塞其他任務(wù))。

          ?

          需要確保只有一個(gè)線程可以訪問特定的程序集。

          ?

          需要有與線程相關(guān)的穩(wěn)定標(biāo)識(shí)。

          Thread 對象包括許多屬性和方法,它們可以幫助控制線程。可以設(shè)置線程的優(yōu)先級,查詢當(dāng)前的線程狀態(tài),中止線程,臨時(shí)阻塞線程,并且執(zhí)行許多其他的線程管理任務(wù)。

          下面的代碼示例演示了如何使用 Thread 對象創(chuàng)建并啟動(dòng)一個(gè)線程。

          static void Main()  
          { 
              System.Threading.Thread workerThread = 
                  new System.Threading.Thread( SomeDelegate ); 
              workerThread.Start(); 
          } 
          public static void SomeDelegate () { Console.WriteLine( "Do some work." ); } 
          

          在這個(gè)示例中,SomeDelegate 是一個(gè) ThreadStart 委托 — 指向?qū)⒁谛戮€程中執(zhí)行的代碼的引用。Thread.Start 向操作系統(tǒng)提交請求以啟動(dòng)線程。

          如果采用這種方式實(shí)例化一個(gè)新線程,就不可能向 ThreadStart 委托傳遞任何參數(shù)。如果需要將一個(gè)參數(shù)傳遞給要在另一個(gè)線程中執(zhí)行的方法,應(yīng)該用所需的方法簽名創(chuàng)建一個(gè)自定義委托并異步調(diào)用它。

          有關(guān)自定義委托的詳細(xì)信息,請參閱本章后面的“使用委托”部分。

          如果需要從單獨(dú)的線程中獲得更新或結(jié)果,可以使用回調(diào)方法 — 一個(gè)委托,引用在線程完成工作之后將要調(diào)用的代碼 — 這就使得線程可以與 UI 交互。有關(guān)詳細(xì)信息,請參閱本章后面的“使用任務(wù)處理 UI 線程和其他線程之間的交互”部分。

          使用委托

          委托是指向方法的引用(或指針)。在定義委托時(shí),可以指定確切的方法簽名,如果其他的方法想要代表該委托,就必須與該簽名相匹配。所有委托都可以同步和異步調(diào)用。

          下面的代碼示例展示了如何聲明委托。這個(gè)示例展示了如何將一個(gè)長期運(yùn)行的計(jì)算實(shí)現(xiàn)為一個(gè)類中的方法。

          delegate string LongCalculationDelegate( int count ); 
          

          如果 .NET Framework 遇到像上面一樣的委托聲明,就隱式聲明了一個(gè)從 MultiCastDelegate 類繼承的隱藏類,正如下面的代碼示例中所示。

          Class LongCalculationDelegate : MutlicastDelegate 
          { 
              public string Invoke( count ); 
              public void BeginInvoke( int count, AsyncCallback callback, 
                  object asyncState ); 
              public string EndInvoke( IAsyncResult result ); 
          } 
          

          委托類型 LongCalculationDelegate 用于引用接受單個(gè)整型參數(shù)并返回一個(gè)字符串的方法。下面的代碼示例舉例說明了一個(gè)這種類型的委托,它引用帶有相關(guān)簽名的特定方法。

          LongCalculationDelegate longCalcDelegate = 
                      new LongCalculationDelegate( calculationMethod ); 
          

          在本示例中,calculationMethod 是實(shí)現(xiàn)您想要在單獨(dú)線程上執(zhí)行的計(jì)算的方法的名稱。

          可以同步或異步調(diào)用委托實(shí)例所引用的方法。為了同步調(diào)用它,可以使用下面的代碼。

          string result = longCalcDelegate( 10000 ); 
          

          該代碼在內(nèi)部使用上面的委托類型中定義的 Invoke 方法。因?yàn)?Invoke 方法是同步調(diào)用,所以此方法只在調(diào)用方法返回之后才返回。返回值是調(diào)用方法的結(jié)果。

          更常見的情況是,為了防止調(diào)用線程阻塞,您將選擇通過使用 BeginInvoke 和 B>EndInvoke 方法來異步調(diào)用委托。異步委托使用 .NET Framework 中的線程池化功能來進(jìn)行線程管理。.NET Framework 實(shí)現(xiàn)的標(biāo)準(zhǔn)異步調(diào)用 模式提供 BeginInvoke 方法來啟動(dòng)線程上所需的操作,并且它提供 EndInvoke 方法來允許完成異步操作以及將任何得到的數(shù)據(jù)傳送回調(diào)用線程。在后臺(tái)處理完成之后,可以調(diào)用回調(diào)方法,其中,可以調(diào)用 EndInvoke 來獲取異步操作的結(jié)果。

          當(dāng)調(diào)用 BeginInvoke 方法時(shí),它不會(huì)等待調(diào)用完成;相反,它會(huì)立即返回一個(gè) IAsyncResult 對象,該對象可以用來監(jiān)視該調(diào)用的進(jìn)度。可以使用 IAsyncResult 對象的 WaitHandle 成員來等待異步調(diào)用完成,或使用 IsComplete 成員輪詢是否完成。如果在調(diào)用完成之前調(diào)用 EndInvoke 方法,它就會(huì)阻塞,并且只在調(diào)用完成之后才返回。然而,您應(yīng)該慎重,不要使用這些技術(shù)來等待調(diào)用完成,因?yàn)樗鼈兛赡茏枞?UI 線程。一般來說,回調(diào)機(jī)制是通知調(diào)用已經(jīng)完成的最好方式。

          異步執(zhí)行委托引用的方法

          1.

          定義代表長期運(yùn)行的異步操作的委托,如下面的示例所示:

          delegate string LongCalculationDelegate( int count ); 
          

          2.

          定義一個(gè)與委托簽名相匹配的方法。下面的示例中的方法模擬需要消耗較多時(shí)間的操作,方法是使線程返回之前睡眠 count 毫秒。

          private string LongCalculation( int count ) 
          { 
              Thread.Sleep( count ); 
              return count.ToString(); 
          } 
          

          3.

          定義與 .NET Framework 定義的 AsyncCallback 委托相對應(yīng)的回調(diào)方法,如下面的示例所示。

          private void CallbackMethod( IAsyncResult ar ) 
          { 
              // Retrieve the invoking delegate. 
              LongCalculationDelegate dlgt = (LongCalculationDelegate)ar.AsyncState; 
              // Call EndInvoke to retrieve the results. 
              string results = dlgt.EndInvoke(ar); 
          } 
          

          4.

          創(chuàng)建一個(gè)委托實(shí)例,它引用您想要異步調(diào)用的方法,并且創(chuàng)建一個(gè) AsyncCallback 委托來引用回調(diào)方法,如下面的代碼示例所示。

              LongCalculationDelegate longCalcDelegate = 
                      new LongCalculationDelegate( calculationMethod ); 
              AsyncCallback callback = new AsyncCallback( CallbackMethod ); 
          

          5.

          從調(diào)用線程中開始異步調(diào)用,方法是調(diào)用引用您想要異步執(zhí)行的代碼的委托中的 BeginInvoke 方法。

              longCalcDelegate.BeginInvoke( count, callback, longCalcDelegate ); 
          

          方法 LongCalculation 是在輔助線程上調(diào)用的。當(dāng)它完成時(shí),就調(diào)用 CallbackMethod 方法,并且獲取計(jì)算的結(jié)果。

          回調(diào)方法是在非 UI 線程上執(zhí)行的。要修改 UI,需要使用某些技術(shù)來從該線程切換到 UI 線程。有關(guān)詳細(xì)信息,請參閱本章后面的“使用任務(wù)處理 UI 線程和其他線程之間的交互”部分

          可以使用自定義委托來將任意參數(shù)傳送給要在單獨(dú)的線程上執(zhí)行的方法(有時(shí)當(dāng)您直接使用 Thread 對象或線程池創(chuàng)建線程時(shí),您無法這樣做)。

          當(dāng)需要在應(yīng)用程序 UI 中調(diào)用長期運(yùn)行的操作時(shí),異步調(diào)用委托非常有用。如果用戶在 UI 中執(zhí)行預(yù)期要花很長時(shí)間才能完成的操作,您肯定并不希望該 UI 凍結(jié),也不希望它不能刷新自己。使用異步委托,可以將控制權(quán)返回給主 UI 線程以執(zhí)行其他操作。

          在以下情況中,您應(yīng)該使用委托來異步調(diào)用方法:

          ?

          需要將任意參數(shù)傳遞給您想要異步執(zhí)行的方法。

          ?

          您想要使用 .NET Framework 提供的異步調(diào)用 模式。

          有關(guān)如何使用 BeginInvoke 和 EndInvoke 進(jìn)行異步調(diào)用的詳細(xì)信息,請參閱http://msdn.microsoft.com/library/en-us/cpguide/html/cpovrasynchronousprogrammingoverview.asp 上的 .NET Framework Developer's Guide 中的“Asynchronous Programming Overview”。

          異步調(diào)用 Web 服務(wù)

          應(yīng)用程序常常使用 Web 服務(wù)與網(wǎng)絡(luò)資源進(jìn)行通信。一般來說,不應(yīng)該從 UI 線程同步調(diào)用 Web 服務(wù),這是因?yàn)?Web 服務(wù)調(diào)用的響應(yīng)時(shí)間變化很大,正如網(wǎng)絡(luò)上所有交互的響應(yīng)時(shí)間的情況一樣。相反,應(yīng)該從客戶端異步調(diào)用所有的 Web 服務(wù)。

          要了解如何異步調(diào)用 Web 服務(wù),可以考慮使用下面簡單的 Web 服務(wù),它睡眠一段時(shí)間,然后返回一個(gè)字符串,指示它已經(jīng)完成了它的操作。

          [WebMethod] 
          public string ReturnMessageAfterDelay( int delay ) 
          { 
              System.Threading.Thread.Sleep(delay); 
              return "Message Received"; 
          } 
          

          當(dāng)在 Microsoft Visual Studio .NET 開發(fā)系統(tǒng)中引用 Web 服務(wù)時(shí),它會(huì)自動(dòng)生成一個(gè)代理。代理是一個(gè)類,它允許使用 .NET Framework 實(shí)現(xiàn)的異步調(diào)用 模式異步調(diào)用 Web 服務(wù)。如果您分析一下生成的代理,您就會(huì)看到下面三個(gè)方法。

          public string ReturnMessageAfterDelay( int delay ) 
          { 
              object[] results = this.Invoke( "ReturnMessageAfterDelay", 
                                              new object[] {delay} ); 
              return ((string)(results[0])); 
          } 
          public System.IAsyncResult BeginReturnMessageAfterDelay( int delay, 
                                    System.AsyncCallback callback, object asyncState ) 
          { 
              return this.BeginInvoke( "ReturnMessageAfterDelay", 
                                       new object[] {delay}, callback, asyncState ); 
          } 
          public string EndReturnMessageAfterDelay( System.IAsyncResult asyncResult ) 
          { 
                object[] results = this.EndInvoke( asyncResult ); 
                return ((string)(results[0])); 
          } 
          

          第一個(gè)方法是調(diào)用 Web 服務(wù)的同步方法。第二個(gè)和第三個(gè)方法是異步方法。可以如下所示異步調(diào)用 Web 服務(wù)。

          private void CallWebService() 
          { 
              localhost.LongRunningService serviceProxy = 
                              new localhost.LongRunningService(); 
              AsyncCallback callback = new AsyncCallback( Completed ); 
              serviceProxy.BeginReturnMessageAfterDelay( callback, serviceProxy, null ); 
          } 
          

          這個(gè)示例非常類似于使用自定義委托的異步回調(diào)示例。當(dāng) Web 服務(wù)返回時(shí),用將要調(diào)用的方法定義一個(gè) AsyncCallback 對象。用指定回調(diào)和代理本身的方法調(diào)用異步 Web 服務(wù),正如下面的代碼示例所示:

          void Completed( IAsyncResult ar ) 
          { 
              localhost.LongRunningService serviceProxy = 
                  (localhost.LongRunningService)ar.AsyncState; 
              string message = serviceProxy.EndReturnMessageAfterDelay( ar ); 
          } 
          

          當(dāng) Web 服務(wù)完成時(shí),就調(diào)用完成的回調(diào)方法。然后,可以通過調(diào)用代理上的 EndReturnMessageAfterDelay 來獲取異步結(jié)果。

          返回頁首返回頁首

          使用任務(wù)處理 UI 線程和其他線程之間的交互

          設(shè)計(jì)多線程應(yīng)用程序最復(fù)雜的一個(gè)方面是處理 UI 線程和其他線程之間的關(guān)系。用于應(yīng)用程序的后臺(tái)線程并不直接與應(yīng)用程序 UI 交互,這一點(diǎn)相當(dāng)關(guān)鍵。如果后臺(tái)線程試圖修改應(yīng)用程序的 UI 中的控件,該控件就可能會(huì)處于一種未知的狀態(tài)。這可能會(huì)在應(yīng)用程序中引起較大的問題,并難于診斷。例如,當(dāng)另一個(gè)線程正在給動(dòng)態(tài)生成的位圖傳送新數(shù)據(jù)時(shí),它或許不能顯示。或者,當(dāng)數(shù)據(jù)集正在刷新時(shí),綁定到數(shù)據(jù)集的組件可能會(huì)顯示沖突信息。

          為了避免這些問題,應(yīng)該從不允許 UI 線程以外的線程更改 UI 控件或綁定到 UI 的數(shù)據(jù)對象。您應(yīng)該始終盡力維護(hù) UI 代碼和后臺(tái)處理代碼之間的嚴(yán)格分離。

          將 UI 線程與其他線程分離是一個(gè)良好的做法,但是您仍然需要在這些線程之間來回傳遞信息。多線程應(yīng)用程序通常需要具有下列功能:

          ?

          從后臺(tái)線程獲得結(jié)果并更新 UI。

          ?

          當(dāng)后臺(tái)線程執(zhí)行它的處理時(shí)向 UI 報(bào)告進(jìn)度。

          ?

          從 UI 控制后臺(tái)線程,例如讓用戶取消后臺(tái)處理。

          從處理后臺(tái)線程的代碼中分離 UI 代碼的有效方法是,根據(jù)任務(wù)構(gòu)造應(yīng)用程序,并且使用封裝所有任務(wù)細(xì)節(jié)的對象代表每個(gè)任務(wù)。

          任務(wù)是用戶期望能夠在應(yīng)用程序內(nèi)完成的一個(gè)工作單元。在多線程處理的環(huán)境中,Task 對象封裝了所有的線程細(xì)節(jié),這樣它們就可以從 UI 中清晰地分離出來。

          通過使用 Task 模式,在使用多線程時(shí)可以簡化代碼。Task 模式將線程管理代碼從 UI 代碼中清晰地分離出來。UI 使用 Task 對象提供的屬性和方法來執(zhí)行行動(dòng),比如啟動(dòng)和停止任務(wù)、以及查詢它們的狀態(tài)。Task 對象也可以提供許多事件,從而允許將狀態(tài)信息傳送回 UI。這些事件都應(yīng)該在 UI 線程內(nèi)激發(fā),這樣,UI 就不需要了解后臺(tái)線程。

          使用 Task 對象可以充分簡化線程交互,Task 對象雖然負(fù)責(zé)控制和管理后臺(tái)線程,但是激發(fā) UI 可以使用并且保證在 UI 線程上的事件。Task 對象可以在應(yīng)用程序的各個(gè)部分中重用,甚至也可以在其他的應(yīng)用程序中重用。

          6.1 說明了使用 Task 模式時(shí)代碼的整體結(jié)構(gòu)。

          multif01

          6.1 使用 Task 模式時(shí)的代碼結(jié)構(gòu)

          Task 模式可以用來在單獨(dú)的線程上執(zhí)行本地后臺(tái)處理任務(wù),或者與網(wǎng)絡(luò)上的遠(yuǎn)程服務(wù)異步交互。在后者的情況下,Task 對象常常稱為服務(wù)代理。服務(wù)代理可以使用與 Task 對象相同的模式,并且可以支持使其與 UI 交互更容易的屬性和事件。

          因?yàn)?Task 對象封裝了任務(wù)的狀態(tài),所以可以用它來更新 UI。要這樣做,無論何時(shí)發(fā)生更改,都可以讓 Task 對象針對主 UI 線程激發(fā) PropertyChanged 事件。這些事件提供一種標(biāo)準(zhǔn)而一致的方法來傳遞屬性值更改。

          可以使用任務(wù)來通知主 UI 線程進(jìn)度或其他狀態(tài)改變。例如,當(dāng)任務(wù)變得可用時(shí),可以將其設(shè)置為已啟用的標(biāo)志,該標(biāo)志可用于啟用相應(yīng)的菜單項(xiàng)和工具欄按鈕。相反,當(dāng)任務(wù)變得不可用(例如,因?yàn)樗€在進(jìn)行中),可以將已啟用標(biāo)志設(shè)置為 false,這會(huì)導(dǎo)致主 UI 線程中的事件處理程序禁用適當(dāng)?shù)牟藛雾?xiàng)和工具欄按鈕。

          也可以使用任務(wù)來更新綁定到 UI 的數(shù)據(jù)對象。應(yīng)該確保數(shù)據(jù)綁定到 UI 控件的任何數(shù)據(jù)對象在 UI 線程上更新。例如,如果將 DataSet 對象綁定到 UI 并從 Web 服務(wù)檢索更新信息,就可以將新數(shù)據(jù)傳遞給 UI 代碼。然后,UI 代碼將新數(shù)據(jù)合并到 UI 線程上綁定的 DataSet 對象中。

          可以使用 Task 對象實(shí)現(xiàn)后臺(tái)處理和線程控制邏輯。因?yàn)?Task 對象封裝了必要的狀態(tài)和數(shù)據(jù),所以它可以協(xié)調(diào)在一個(gè)或更多線程中完成任務(wù)所需的工作,并且在需要時(shí)傳遞更改和通知到應(yīng)用程序的 UI。可以實(shí)現(xiàn)所有必需的鎖定和同步并將其封裝在 Task 對象中,這樣 UI 線程就不必處理這些問題。

          定義 Task 類

          下面的代碼示例顯示了管理長期計(jì)算任務(wù)的類定義。

          雖然該示例比較簡單,但是它可以很容易地?cái)U(kuò)展為支持在應(yīng)用程序的 UI 中集成的復(fù)雜后臺(tái)任務(wù)。

          public class CalculationTask 
          { 
              // Class Members…    public CalculationTask(); 
              public void StartCalculation( int count ); 
              public void StopCalculation(); 
              private void FireStatusChangedEvent( CalculationStatus status ); 
              private void FireProgressChangedEvent( int progress ); 
              private string Calculate( int count ); 
              private void EndCalculate( IAsyncResult ar ); 
          } 
          

          CalculationTask 類定義一個(gè)默認(rèn)的構(gòu)造函數(shù)和兩個(gè)公共方法來啟動(dòng)和停止計(jì)算。它還定義了幫助器方法來幫助 Task 對象激發(fā)針對 UI 的事件。Calculate 方法實(shí)現(xiàn)計(jì)算邏輯,并且運(yùn)行在后臺(tái)線程上。EndCalculate 方法實(shí)現(xiàn)回調(diào)方法,它是在后臺(tái)計(jì)算線程完成之后調(diào)用的。

          類成員如下:

          private CalculationStatus _calcState; 
          private delegate string CalculationDelegate( int count ); 
          public delegate void CalculationStatusEventHandler( 
                          object sender, CalculationEventArgs e ); 
          public delegate void CalculationProgressEventHandler( 
                          object sender, CalculationEventArgs e ); 
          public event CalculationStatusEventHandler CalculationStatusChanged; 
          public event CalculationProgressEventHandler CalculationProgressChanged; 
          

          CalculationStatus 成員是一個(gè)枚舉,它定義了在任何一個(gè)時(shí)刻計(jì)算可能處于的三個(gè)狀態(tài)。

          public enum CalculationStatus 
          { 
              NotCalculating, 
              Calculating, 
              CancelPending 
          } 
          

          Task 類提供兩個(gè)事件:一個(gè)通知 UI 有關(guān)計(jì)算狀態(tài)的事件,另一個(gè)通知 UI 有關(guān)計(jì)算進(jìn)度的事件。委托簽名與事件本身都要定義。

          這兩個(gè)事件是在幫助器方法中激發(fā)的。這些方法檢查目標(biāo)的類型,如果目標(biāo)類型是從 Control 類派生的,它們就使用 Control 類中的 Invoke 方法來激發(fā)事件。因此,對于 UI 事件接收器,可以保證事件是在 UI 線程上調(diào)用的。下面的示例展示了激發(fā)事件的代碼。

          private void FireStatusChangedEvent( CalculationStatus status ) 
          { 
              if( CalculationStatusChanged != null ) 
              { 
                  CalculationEventArgs args = new CalculationEventArgs( status ); 
                  if ( CalculationStatusChanged.Target is 
                          System.Windows.Forms.Control ) 
                  { 
                      Control targetForm = CalculationStatusChanged.Target 
                              as System.Windows.Forms.Control; 
                      targetForm.Invoke( CalculationStatusChanged, 
                              new object[] { this, args } ); 
                  } 
                  else 
                  { 
                      CalculationStatusChanged( this, args ); 
                  } 
              } 
          } 
          

          這段代碼首先檢查事件接收器是否已經(jīng)注冊,如果它已經(jīng)注冊,就檢查目標(biāo)的類型。如果目標(biāo)類型是從 Control 類派生的,就使用 Invoke 方法激發(fā)該事件以確保在 UI 線程上處理它。如果目標(biāo)類型不是從 Control 類派生的,就正常激發(fā)事件。在 FireProgressChangedEvent 方法中,以相同的方式激發(fā)事件以向 UI 報(bào)告計(jì)算進(jìn)度,如下列的示例所示。

          private void FireProgressChangedEvent( int progress ) 
              { 
                  if( CalculationProgressChanged != null ) 
                  { 
                      CalculationEventArgs args = 
                          new CalculationEventArgs( progress ); 
                      if ( CalculationStatusChanged.Target is 
                              System.Windows.Forms.Control ) 
                      { 
                          Control targetForm = CalculationStatusChanged.Target 
                                  as System.Windows.Forms.Control; 
                          targetForm.Invoke( CalculationProgressChanged, 
                                  new object[] { this, args } ); 
                      } 
                      else 
                      { 
                          CalculationProgressChanged( this, args ); 
                      } 
                  } 
          } 
          

          CalculationEventArgs 類定義了兩個(gè)事件的事件參數(shù),并且包含計(jì)算狀態(tài)和進(jìn)度參數(shù),以便將它們發(fā)送給 UI。CalculationEventArgs 類的定義如下所示。

          public class CalculationEventArgs : EventArgs 
              { 
                  public string            Result; 
                  public int               Progress; 
                  public CalculationStatus Status; 
                  public CalculationEventArgs( int progress ) 
                  { 
                      this.Progress = progress; 
                      this.Status   = CalculationStatus.Calculating; 
                  } 
                  public CalculationEventArgs( CalculationStatus status ) 
                  { 
                      this.Status = status; 
                  } 
          }     
          

          StartCalculation 方法負(fù)責(zé)啟動(dòng)后臺(tái)線程上的計(jì)算。委托 CalculationDelegate 允許使用委托異步調(diào)用 (Delegate Asynchronous Call) 模式在后臺(tái)線程上調(diào)用 Calculation 方法,如下面的示例所示。

          public void StartCalculation( int count ) 
          { 
              lock( this ) 
              { 
                  if( _calcState == CalculationStatus.NotCalculating ) 
                  { 
                      // Create a delegate to the calculation method. 
                      CalculationDelegate calc = 
                              new CalculationDelegate( Calculation ); 
                      // Start the calculation. 
                      calc.BeginInvoke( count, 
                              new AsyncCallback( EndCalculate ), calc ); 
                      // Update the calculation status. 
                      _calcState = CalculationStatus.Calculating; 
                      // Fire a status changed event. 
                      FireStatusChangedEvent( _calcState ); 
                  } 
              } 
          } 
          

          StopCalculation 方法負(fù)責(zé)取消計(jì)算,如下面的代碼示例所示。

          public void StopCalculation() 
          { 
              lock( this ) 
              { 
                  if( _calcState == CalculationStatus.Calculating ) 
                  { 
                      // Update the calculation status. 
                      _calcState = CalculationStatus.CancelPending; 
                      // Fire a status changed event. 
                      FireStatusChangedEvent( _calcState ); 
                  } 
              } 
          } 
          

          當(dāng)調(diào)用 StopCalculation 時(shí),計(jì)算狀態(tài)被設(shè)置為 CancelPending,以通知后臺(tái)停止計(jì)算。向 UI 激發(fā)一個(gè)事件,以通知已經(jīng)接收到取消請求。

          這兩個(gè)方法都使用 lock 關(guān)鍵字來確保對計(jì)算狀態(tài)變量的更改是原子的,這樣應(yīng)用程序就不會(huì)遇到爭用情形。這兩個(gè)方法都激發(fā)狀態(tài)改變事件來通知 UI 計(jì)算正在啟動(dòng)或停止。

          計(jì)算方法定義如下。

          private string Calculation( int count ) 
          { 
              string result = ""; 
              for ( int i = 0 ; i < count ; i++ ) 
              { 
                  // Long calculation…        // Check for cancel. 
                  if ( _calcState == CalculationStatus.CancelPending ) break; 
                  // Update Progress 
                  FireProgressChangedEvent( count, i ); 
              } 
              return result; 
          } 
          

          注為清楚起見,計(jì)算的細(xì)節(jié)已經(jīng)忽略。

          每次傳遞都是通過循環(huán)進(jìn)行的,這樣就可以檢查計(jì)算狀態(tài)成員,以查看用戶是否已經(jīng)取消了計(jì)算。如果這樣,循環(huán)就退出,從而完成計(jì)算方法。如果計(jì)算繼續(xù)進(jìn)行,就使用 FireProgressChanged 幫助器方法來激發(fā)事件,以向 UI 報(bào)告進(jìn)度。

          在計(jì)算完成之后,就調(diào)用 EndCalculate 方法,以便通過調(diào)用 EndInvoke 方法來完成異步調(diào)用,如下面的示例所示。

          private void EndCalculate( IAsyncResult ar ) 
          { 
              CalculationDelegate del = (CalculationDelegate)ar.AsyncState; 
              string result = del.EndInvoke( ar ); 
              lock( this ) 
              { 
                  _calcState = CalculationStatus.NotCalculating; 
                  FireStatusChangedEvent( _calcState ); 
              } 
          } 
          

          EndCalculate 將計(jì)算狀態(tài)重置為 NotCalculating,準(zhǔn)備開始下一次計(jì)算。同時(shí),它激發(fā)一個(gè)狀態(tài)改變事件,這樣就可以通知 UI 計(jì)算已經(jīng)完成。

          使用 Task 類

          Task 類負(fù)責(zé)管理后臺(tái)線程。要使用 Task 類,必須做的事情就是創(chuàng)建一個(gè) Task 對象,注冊它激發(fā)的事件,并且實(shí)現(xiàn)這些事件的處理。因?yàn)槭录窃?UI 線程上激發(fā)的,所以您根本不必?fù)?dān)心代碼中的線程處理問題。

          下面的示例展示了如何創(chuàng)建 Task 對象。在這個(gè)示例中,UI 有兩個(gè)按鈕,一個(gè)用于啟動(dòng)計(jì)算,一個(gè)用于停止計(jì)算,還有一個(gè)進(jìn)度欄顯示當(dāng)前的計(jì)算進(jìn)度。

          // Create new task object to manage the calculation. 
          _calculationTask = new CalculationTask(); 
          // Subscribe to the calculation status event. 
          _ calculationTask.CalculationStatusChanged += new 
            CalculationTask.CalculationStatusEventHandler( OnCalculationStatusChanged ); 
          // Subscribe to the calculation progress event. 
          _ calculationTask.CalculationProgressChanged += new 
            CalculationTask.CalculationProgressEventHandler(  
          OnCalculationProgressChanged ); 
          

          用于計(jì)算狀態(tài)和計(jì)算進(jìn)度事件的事件處理程序相應(yīng)地更新 UI,例如通過更新狀態(tài)欄控件。

          private void CalculationProgressChanged( object sender,  
          CalculationEventArgs e ) 
          { 
              _progressBar.Value = e.Progress; 
          } 
          

          下面的代碼展示的 CalculationStatusChanged 事件處理程序更新進(jìn)度欄的值以反映當(dāng)前的計(jì)算進(jìn)度。假定進(jìn)度欄的最小值和最大值已經(jīng)初始化。

          private void CalculationStatusChanged( object sender, CalculationEventArgs e ) 
          { 
              switch ( e.Status ) 
              { 
                  case CalculationStatus.Calculating: 
                      button1.Enabled = false; 
                      button2.Enabled = true; 
                      break; 
                  case CalculationStatus.NotCalculating: 
                      button1.Enabled = true; 
                      button2.Enabled = false; 
                      break; 
                  case CalculationStatus.CancelPending: 
                      button1.Enabled = false; 
                      button2.Enabled = false; 
                      break; 
              } 
          } 
          

          在這個(gè)示例中,CalculationStatusChanged 事件處理程序根據(jù)計(jì)算狀態(tài)啟用和禁用啟動(dòng)和停止按鈕。這可以防止用戶嘗試啟動(dòng)一個(gè)已經(jīng)在進(jìn)行的計(jì)算,并且向用戶提供有關(guān)計(jì)算狀態(tài)的反饋。

          通過使用 Task 對象中的公共方法,UI 為每個(gè)按鈕單擊實(shí)現(xiàn)了窗體事件處理程序,以便啟動(dòng)和停止計(jì)算。例如,啟動(dòng)按鈕事件處理程序調(diào)用 StartCalculation 方法,如下所示。

          private void startButton_Click( object sender, System.EventArgs e ) 
          { 
              calculationTask.StartCalculation( 1000 ); 
          } 
          

          類似地,停止計(jì)算按鈕通過調(diào)用 StopCalculation 方法來停止計(jì)算,如下所示。

          private void stopButton_Click( object sender, System.EventArgs e ) 
          { 
              calculationTask.StopCalculation(); 
          } 
          
          返回頁首返回頁首

          小結(jié)

          多線程處理是創(chuàng)建可以響應(yīng)的智能客戶端應(yīng)用程序的重要部分。應(yīng)該分析多線程適合于應(yīng)用程序的什么地方,并且注意在單獨(dú)線程上進(jìn)行不直接涉及 UI 的所有處理。在大多數(shù)情況下,可以使用 ThreadPool 類創(chuàng)建線程。然而,在某些情況下,必須使用 Thread 類來作為代替,在另外一些情況下,需要使用委托對象或 Web 服務(wù)代理來使特定的處理在非 UI 線程上進(jìn)行。

          在多線程應(yīng)用程序中,必須確保 UI 線程負(fù)責(zé)所有與 UI 有關(guān)的任務(wù),這樣就可以有效地管理 UI 線程和其他線程之間的通信。Task 模式可以幫助大大簡化這種交互。

          轉(zhuǎn)到原英文頁面

          posted on 2006-02-10 14:56 TrampEagle 閱讀(287) 評論(0)  編輯  收藏 所屬分類: 技術(shù)文摘
          主站蜘蛛池模板: 新郑市| 蒙自县| 盱眙县| 桂平市| 聊城市| 景谷| 肇州县| 怀集县| 泰安市| 荣昌县| 淄博市| 莲花县| 北票市| 阜平县| 略阳县| 开阳县| 乐安县| 柳林县| 德江县| 桐庐县| 永安市| 铜川市| 莱阳市| 思南县| 柳河县| 大新县| 吕梁市| 恩施市| 新田县| 合作市| 垣曲县| 宜丰县| 吉首市| 潢川县| 保山市| 陆良县| 江门市| 长葛市| 淮北市| 滨海县| 延川县|