C#多線程技術-文檔資料_第1頁
C#多線程技術-文檔資料_第2頁
C#多線程技術-文檔資料_第3頁
C#多線程技術-文檔資料_第4頁
C#多線程技術-文檔資料_第5頁
已閱讀5頁,還剩18頁未讀 繼續免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

1、1第第9章章 C#多線程技術多線程技術9.1線程概述使用C#編寫任何程序時,都有一個入口:Main()方法。程序從Main()方法的第一條語句開始執行,直到這個方法返回為止。這樣的程序結構非常適合于有一個可識別的任務序列的程序,但程序常常需要同時完成多個任務。例如在使用文字處理軟件的時候,用戶在輸入文字的同時,軟件能同步進行拼寫檢查而不需要用戶的等待;再如在一個應用程序的打印功能中,如果程序只能執行一個任務序列,用戶可能需要等待所有的打印任務完成后才能繼續操作,這時就需要能讓程序同時處理多個任務的能力。在C#應用程序中,第一個線程總是Main()方法,因為第一個線程是由.NET運行庫開始執行的

2、,Main()方法是.NET運行庫選擇的第一個方法。后續的線程由應用程序在內部啟動,即應用程序可以創建和啟動新的線程。 29.2 .NET對多線程的支持對多線程的支持 在.NET程序設計中,線程是使用Thread類來處理的,該類在System.Threading命名空間中。一個Thread實例管理一個線程,即執行序列。通過簡單實例化一個Thread對象,就可以創建一個線程,然后通過Thread對象提供的方法對線程進行管理。 9.2.1 線程的建立與啟動線程的建立與啟動假定我們需要編寫一個文件壓縮軟件,用戶點擊壓縮按鈕后開始壓縮指定的文件。因為整個壓縮過程需要一定的時間才能完成,而用戶此時還可能

3、需要移動或縮放程序的窗口,甚至暫停或中止當前文件的壓縮。此時一般需要創建一個單獨的線程來處理這個壓縮過程使得在壓縮過程中可以不中斷用戶界面的響應。因此,我們需要實例化一個Thread對象來創建這個線程:/假設DoCompress是前面已經聲明了的一個ThreadStart委托Thread compressThread = New Thread(entryPoint);這段代碼指定線程對象的實例名為compressThread。在一個應用程序中創建另一個線程,執行一些任務,通常稱為工作線程(worker thread),這里compressThread就是一個工作線程,而Main()方法所在的線

4、程常被稱為主線程。39.2.1 線程的建立與啟動線程的建立與啟動從代碼可以看出,Thread構造函數需要一個參數,用于指定線程的入口即線程開始執行的方法,因為我們傳送的是方法的詳細信息,所以需要使用委托。實際上,該委托已經在System.Threading命名空間中定義好了。它稱為ThreadStart,其聲明如下所示: public delegate void ThreadStart();傳送給構造函數的參數必須是這種類型的委托。上面的例子中是entryPoint,我們來看如何定義這個委托:/ 實際線程執行的方法static void DoCompress() / 壓縮代碼ThreadSta

5、rt entryPoint = new ThreadStart(DoCompress);線程對象建立完成后,新線程實際上并沒有執行任務,它只是在等待執行。我們需要顯式地調用Thread對象的Start()方法來啟動線程:compressThread.Start();此外還可以使用Thread對象的Name屬性給線程賦予一個友好的名稱。49.2.2 線程的掛起、恢復與終止線程的掛起、恢復與終止啟動了一個線程后,線程將運行到所在的方法結束為止,在此期間還可以掛起、恢復或中止它。掛起一個線程就是讓它進入睡眠狀態,此時,線程僅是停止運行某段時間,不占用任何處理器時間,以后還可以恢復,從被掛起的那個狀態

6、重新運行。如果線程被中止,就是停止運行,Windows會永久地刪除該線程的所有數據,所以該線程不能重新啟動。 繼續上面的文件壓縮例子,假定由于某些原因,用戶界面線程顯示一個對話框,允許用戶選擇臨時暫停壓縮過程。在主線程中編寫如下響應:compressThread.Suspend(); 如果用戶以后要求恢復該線程,可以使用下面的方法:CompressThread.Resume() 最后,如果用戶決定不需要繼續壓縮的話,單擊取消按鈕,可以使用下面的方法:CompressThread.Abort()59.4 線程的優先級線程的優先級如果在應用程序中有多個線程在運行,但一些線程比另一些線程重要因而需要

7、分配更多的CPU時間該怎么辦?在這種情況下,可以在一個進程中為不同的線程指定不同的優先級。一般情況下,如果有優先級較高的線程在工作,就不會給優先級較低的線程分配任何時間片,其優點是可以保證給接收用戶輸入的線程指定較高的優先級。在大多數的時間內,這個線程什么也不做,而其他線程則執行它們的任務。但是,如果用戶輸入了信息,這個線程就立即獲得比應用程序中其他線程更高的優先級,在短時間內處理用戶輸入控件。線程的優先級定義為ThreadPriority枚舉類型,取值如表9.1所示:表表9.1 線程的優先級及其含義線程的優先級及其含義69.4 線程的優先級線程的優先級高優先級的線程可以完全阻止低優先級的線程

8、執行,因此在改變線程的優先級時要特別小心以免造成某些線程得不到CPU時間。此外,每個進程都有個基本優先級,這些值與進程的優先級是有關系的。給線程指定較高的優先級,可以確保它在該進程內比其他線程優先執行,但系統上可能還運行著其他進程,它們的線程有更高的優先級。如Windows給自己的操作系統線程指定高優先級。在【例9.1】中,對Main()方法做如下修改,就可以看出修改線程的優先級的效果:/ 建立新線程對象ThreadStart workerStart = new ThreadStart(DisplayNumbers);Thread workerThread = new Thread(worke

9、rStart);workerThread.Name = Worker Thread;workerThread.Priority = AboveNormal;79.4 線程的優先級線程的優先級其中通過代碼設置工作線程的優先級比主線程高,運行結果如下所示:請輸入一個數字:1000000線程: Main Thread 已開始運行.Main Thread: 當前計數為 1000000Main Thread: 當前計數為 2000000Main Thread: 當前計數為 3000000Main Thread: 當前計數為 4000000Main Thread: 當前計數為 5000000Main Th

10、read: 當前計數為 6000000線程: Worker Thread 已開始運行.Worker Thread: 當前計數為 1000000Worker Thread: 當前計數為 2000000Worker Thread: 當前計數為 300000089.4 線程的優先級線程的優先級Worker Thread: 當前計數為 4000000Worker Thread: 當前計數為 5000000Worker Thread: 當前計數為 6000000Worker Thread: 當前計數為 7000000Worker Thread: 當前計數為 8000000線程 Worker Thread

11、 完成.Main Thread: 當前計數為 7000000Main Thread: 當前計數為 8000000線程 Main Thread 完成.這說明,當工作線程的優先級為AboveNormal時,一旦工作線程被啟動,主線程就不再運行,直到工作線程結束后主線程才重新計算。讓我們繼續試驗操作系統如何對線程分配CPU時間:99.4 線程的優先級線程的優先級在DisplayNumbers()方法的循環體中加上一句代碼,:if(i%interval = 0)Console.WriteLine(thisThread.Name + : 當前計數為 + i);Thread.Sleep(10);/ 讓當前

12、工作線程暫停10毫秒現在來看運行結果:請輸入一個數字:1000000線程: Main Thread 已開始運行.Main Thread: 當前計數為 1000000線程: Worker Thread 已開始運行.Worker Thread: 當前計數為 1000000Main Thread: 當前計數為 2000000Main Thread: 當前計數為 3000000Worker Thread: 當前計數為 2000000Main Thread: 當前計數為 4000000Worker Thread: 當前計數為 3000000Worker Thread: 當前計數為 4000000109.

13、4 線程的優先級線程的優先級Main Thread: 當前計數為 5000000Worker Thread: 當前計數為 5000000Worker Thread: 當前計數為 6000000Main Thread: 當前計數為 6000000Worker Thread: 當前計數為 7000000Worker Thread: 當前計數為 8000000線程 Worker Thread 完成.Main Thread: 當前計數為 7000000Main Thread: 當前計數為 8000000線程 Main Thread 完成.此時的結果與前面有很大的不同,雖然工作線程仍然早于主線程完成,但

14、是在工作線程的計算過程中,主線程也獲到了CPU時間。這是因為在DisplayNumbers()方法中使用的Thread靜態方法Sleep()放棄了CPU時間,即使當前線程具有較高的優先級,操作系統也會把時間片分配給其他優先級低的線程。如果我們把Sleep()的參數加到100毫秒,運行結果又會有很大的不同,甚至可能兩個線程是幾乎并行完成的。119.5 線程同步線程同步使用線程的一個重要方面是同步訪問多個線程訪問的任何變量。所謂同步,是指在某一時刻只有一個線程可以訪問變量。如果不能確保對變量的訪問是同步的,就可能會產生錯誤或不可預料的結果。一般情況下,當一個線程寫入一個變量,同時有其他線程讀取或寫

15、入這個變量時,就應同步變量。本節將簡要介紹同步的一些主要內容。9.5.1 同步的含義同步的含義同步問題的產生,主要是由于在高級語言的源代碼中,大多數情況下看起來是一條語句,但在最后編譯好的匯編語言機器碼中則會被翻譯為許多條語句,從而在操作系統調度時被劃分到不同的時間片中。129.5.1 同步的含義同步的含義看看下面這個語句,假設message是一個string對象,已經保存了一個字符串:message += Hello world!;這條語句在C#語法上是一條語句,但在執行代碼時,實際上它涉及到許多操作。需要重新分配內存以存儲更長的新字符串,需要設置變量message使之指向新的內存,需要復制

16、實際文本等。顯然,這里選擇了一種復雜字符串,但即使在基本數字類型上執行算術操作,后臺進行的操作也比從C#代碼中看到的要多。而且,許多操作不能直接在存儲于內存空間中的變量上進行,它們的值必須單獨復制到處理器的特定位置上,即寄存器。只要一個C#語句翻譯為多個本機代碼命令,線程的時間片就有可能在執行該語句的進程中終止,如果是這樣,同一個進程中的另一個線程就會獲得一個時間片,如果涉及到這條語句的變量訪問(在上面的示例中,是message)不是同步的,那么另一個線程可能讀寫同一個變量。 139.5.2 在在C#中處理同步中處理同步通過對指定對象的加鎖和解鎖可以同步代碼段的訪問。在.NET的System.

17、Threading命名空間中提供了Monitor類來實現加鎖與解鎖。這個類中的方法都是靜態的,所以不需要實例化這個類。表9.2中一些靜態的方法提供了一種機制用來同步對象的訪問從而避免死鎖和維護數據的一致性。 表表9.2 Monitor類的主要方法類的主要方法149.5.2 在在C#中處理同步中處理同步以下是使用Monitor類的簡單例子:public void some_method()/ 獲取鎖Monitor.Enter(this); / 處理需要同步的代碼. / 釋放鎖 Monitor.Exit(this); 上面的代碼運行可能會產生問題。當代碼運行到獲取鎖與釋放鎖之間時一旦發生異常,Mo

18、nitor.Exit將不會返回。這段程序將掛起,其他的線程也將得不到鎖。解決方法是:將代碼放入tryfinally內,在finally調用Monitor.Exit,這樣的話最后一定會釋放鎖。 159.5.2 在在C#中處理同步中處理同步C# lock關鍵字提供了與Monitoy.Enter和Monitoy.Exit同樣的功能,這種方法用在你的代碼段不能被其他獨立的線程中斷的情況。通過對Monitor類的簡易封裝,lock為同步訪問變量提供了一個非常簡單的方式,其用法如下:lock(x)/ 使用x的語句lock語句把變量放在圓括號中,以包裝對象,稱為獨占鎖或排它鎖。當執行帶有lock關鍵字的復合

19、語句時,獨占鎖會保留下來。當變量被包裝在獨占鎖中時,其他線程就不能訪問該變量。如果在上面的代碼中使用獨占鎖,在執行復合語句時,這個線程就會失去其時間片。如果下一個獲得時間片的線程試圖訪問變量,就會被拒絕。Windows會讓其他線程處于睡眠狀態,直到解除了獨占鎖為止。169.5.2 在在C#中處理同步中處理同步【例【例9.2】使用lock同步線程。本示例建立了10個線程using System;using System.Threading;/銀行帳戶類class Account int balance;/余額Random r = new Random();public Account(int i

20、nitial) balance = initial;/ 取錢int Withdraw(int amount) if (balance = amount) Console.WriteLine(原有余額: + balance);Console.WriteLine(支取金額: - + amount); balance = balance - amount;Console.WriteLine(現有余額: + balance);return amount;else return 0;/ 拒絕交易18【例【例9.2】 / 測試交易public void DoTransactions() / 支取隨機的金額

21、100次for (int i = 0; i 100; i+) Withdraw(r.Next(1, 100);class TestApppublic static void Main() /建立10個線程同時進行交易Thread threads = new Thread10;Account acc = new Account (1000);for (int i = 0; i 10; i+) 19【例【例9.2】 Thread t = new Thread(new ThreadStart(acc.DoTransactions);threadsi = t;for (int i = 0; i 10;

22、 i+) threadsi.Start();在這個示例中,10個線程同時進行交易,如果不加控制,很可能發生在支取金額時對balance字段的訪問沖突。假設當前余額為100,有兩個線程都要支取60,則各自檢查余額時都認為可以支取,造成的后果則是總共被支取120,從而導致余額為負值。讀者可以試著將lock語句注釋掉再運行,此時將產生余額為負的異常。 209.5.3 同步時要注意的問題同步時要注意的問題同步線程在多線程應用程序中非常重要。但是,這是一個需要詳細討論的內容,因為很容易出現微妙且難以察覺的問題,特別是死鎖。線程同步非常重要,但只在需要時使用也是非常重要的。因為這會降低性能。原因有兩個:首

23、先,在對象上放置和解開鎖會帶來某些系統開銷,但這些系統開銷都非常小。第二個原因更為重要,線程同步使用得越多,等待釋放對象的線程就越多。如果一個線程在對象上放置了一個鎖,需要訪問該對象的其他線程就只能暫停執行,直到該鎖被解開,才能繼續執行。因此,在lock塊內部編寫的代碼越少越好,以免出現線程同步錯誤。lock語句在某種意義上就是臨時禁用應用程序的多線程功能,也就臨時刪除了多線程的各種優勢。另一方面,使用過多的同步線程的危險性(性能和響應降低)并沒有在需要時不使用同步線程那么高(難以跟蹤的運行時錯誤)。219.5.3 同步時要注意的問題同步時要注意的問題死鎖是一個錯誤,在兩個線程都需要訪問被互鎖的資源時發生。假定一個線程運行下述代碼,其中a和b是兩個線程都可以訪問的對象引用:lock(a)(lock(b)/ do something同時,另一個線程運行下述代碼:lock(b)(lock(a)/ do something229.5.3

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論