多線程(基礎篇3)

来源:http://www.cnblogs.com/yonghuacui/archive/2016/12/17/6187701.html
-Advertisement-
Play Games

在上一篇多線程(基礎篇2)中,我們主要講述了確定線程的狀態、線程優先順序、前臺線程和後臺線程以及向線程傳遞參數的知識,在這一篇中我們將講述如何使用C#的lock關鍵字鎖定線程、使用Monitor鎖定線程以及線程中的異常處理。 九、使用C#的lock關鍵字鎖定線程 1、使用Visual Studio 2 ...


  在上一篇多線程(基礎篇2)中,我們主要講述了確定線程的狀態、線程優先順序、前臺線程和後臺線程以及向線程傳遞參數的知識,在這一篇中我們將講述如何使用C#的lock關鍵字鎖定線程、使用Monitor鎖定線程以及線程中的異常處理。

九、使用C#的lock關鍵字鎖定線程

1、使用Visual Studio 2015創建一個新的控制台應用程式。

2、雙擊打開“Program.cs”文件,然後修改為如下代碼:

 1 using System;
 2 using System.Threading;
 3 using static System.Console;
 4 
 5 namespace Recipe09
 6 {
 7     abstract class CounterBase
 8     {
 9         public abstract void Increment();
10         public abstract void Decrement();
11     }
12 
13     class Counter : CounterBase
14     {
15         public int Count { get; private set; }
16 
17         public override void Increment()
18         {
19             Count++;
20         }
21 
22         public override void Decrement()
23         {
24             Count--;
25         }
26     }
27 
28     class CounterWithLock : CounterBase
29     {
30         private readonly object syncRoot = new Object();
31 
32         public int Count { get; private set; }
33 
34         public override void Increment()
35         {
36             lock (syncRoot)
37             {
38                 Count++;
39             }
40         }
41 
42         public override void Decrement()
43         {
44             lock (syncRoot)
45             {
46                 Count--;
47             }
48         }
49     }
50 
51     class Program
52     {
53         static void TestCounter(CounterBase c)
54         {
55             for (int i = 0; i < 100000; i++)
56             {
57                 c.Increment();
58                 c.Decrement();
59             }
60         }
61 
62         static void Main(string[] args)
63         {
64             WriteLine("Incorrect counter");
65             var c1 = new Counter();
66             var t1 = new Thread(() => TestCounter(c1));
67             var t2 = new Thread(() => TestCounter(c1));
68             var t3 = new Thread(() => TestCounter(c1));
69             t1.Start();
70             t2.Start();
71             t3.Start();
72             t1.Join();
73             t2.Join();
74             t3.Join();
75             WriteLine($"Total count: {c1.Count}");
76 
77             WriteLine("--------------------------");
78 
79             WriteLine("Correct counter");
80             var c2 = new CounterWithLock();
81             t1 = new Thread(() => TestCounter(c2));
82             t2 = new Thread(() => TestCounter(c2));
83             t3 = new Thread(() => TestCounter(c2));
84             t1.Start();
85             t2.Start();
86             t3.Start();
87             t1.Join();
88             t2.Join();
89             t3.Join();
90             WriteLine($"Total count: {c2.Count}");
91         }
92     }
93 }

3、運行該控制台應用程式,運行效果(每次運行效果可能不同)如下圖所示:

  在第65行代碼處,我們創建了Counter類的一個對象,該類定義了一個簡單的counter變數,該變數可以自增1和自減1。然後在第66~68行代碼處,我們創建了三個線程,並利用lambda表達式將Counter對象傳遞給了“TestCounter”方法,這三個線程共用同一個counter變數,並且對這個變數進行自增和自減操作,這將導致結果的不正確。如果我們多次運行這個控制台程式,它將列印出不同的counter值,有可能是0,但大多數情況下不是。

  發生這種情況是因為Counter類是非線程安全的。我們假設第一個線程在第57行代碼處執行完畢後,還沒有執行第58行代碼時,第二個線程也執行了第57行代碼,這個時候counter的變數值自增了2次,然後,這兩個線程同時執行了第58行處的代碼,這會造成counter的變數只自減了1次,因此,造成了不正確的結果。

  為了確保不發生上述不正確的情況,我們必須保證在某一個線程訪問counter變數時,另外所有的線程必須等待其執行完畢才能繼續訪問,我們可以使用lock關鍵字來完成這個功能。如果我們在某個線程中鎖定一個對象,其他所有線程必須等到該線程解鎖之後才能訪問到這個對象,因此,可以避免上述情況的發生。但是要註意的是,使用這種方式會嚴重影響程式的性能。更好的方式我們將會在仙童同步中講述。

十、使用Monitor鎖定線程

   在這一小節中,我們將描述一個多線程編程中的常見的一個問題:死鎖。我們首先創建一個死鎖的示例,然後使用Monitor避免死鎖的發生。

1、使用Visual Studio 2015創建一個新的控制台應用程式。

2、雙擊打開“Program.cs”文件,編寫代碼如下:

 1 using System;
 2 using System.Threading;
 3 using static System.Console;
 4 using static System.Threading.Thread;
 5 
 6 namespace Recipe10
 7 {
 8     class Program
 9     {
10         static void LockTooMuch(object lock1, object lock2)
11         {
12             lock (lock1)
13             {
14                 Sleep(1000);
15                 lock (lock2)
16                 {
17                 }
18             }
19         }
20 
21         static void Main(string[] args)
22         {
23             object lock1 = new object();
24             object lock2 = new object();
25 
26             new Thread(() => LockTooMuch(lock1, lock2)).Start();
27 
28             lock (lock2)
29             {
30                 WriteLine("This will be a deadlock!");
31                 Sleep(1000);
32                 lock (lock1)
33                 {
34                     WriteLine("Acquired a protected resource succesfully");
35                 }
36             }
37         }
38     }
39 }

3、運行該控制台應用程式,運行效果如下圖所示:

  在上述結果中我們可以看到程式發生了死鎖,程式一直結束不了。

  在第10~19行代碼處,我們定義了一個名為“LockTooMuch”的方法,在該方法中我們鎖定了第一個對象lock1,等待1秒鐘後,希望鎖定第二個對象lock2。

  在第26行代碼處,我們創建了一個新的線程來執行“LockTooMuch”方法,然後立即執行第28行代碼。

  在第28~32行代碼處,我們在主線程中鎖定了對象lock2,然後等待1秒鐘後,希望鎖定第一個對象lock1。

  在創建的新線程中我們鎖定了對象lock1,等待1秒鐘,希望鎖定對象lock2,而這個時候對象lock2已經被主線程鎖定,所以新建線程會等待對象lock2被主線程解鎖。然而,在主線程中,我們鎖定了對象lock2,等待1秒鐘,希望鎖定對象lock1,而這個時候對象lock1已經被創建的線程鎖定,所以主線程會等待對象lock1被創建的線程解鎖。當發生這種情況的時候,死鎖就發生了,所以我們的控制台應用程式目前無法正常結束。

4、要避免死鎖的發生,我們可以使用“Monitor.TryEnter”方法來替換lock關鍵字,“Monitor.TryEnter”方法在請求不到資源時不會阻塞等待,可以設置超時時間,獲取不到直接返回false。修改代碼如下所示:

 1 using System;
 2 using System.Threading;
 3 using static System.Console;
 4 using static System.Threading.Thread;
 5 
 6 namespace Recipe10
 7 {
 8     class Program
 9     {
10         static void LockTooMuch(object lock1, object lock2)
11         {
12             lock (lock1)
13             {
14                 Sleep(1000);
15                 lock (lock2)
16                 {
17                 }
18             }
19         }
20 
21         static void Main(string[] args)
22         {
23             object lock1 = new object();
24             object lock2 = new object();
25 
26             new Thread(() => LockTooMuch(lock1, lock2)).Start();
27 
28             lock (lock2)
29             {
30                 WriteLine("This will be a deadlock!");
31                 Sleep(1000);
32                 //lock (lock1)
33                 //{
34                 //    WriteLine("Acquired a protected resource succesfully");
35                 //}
36                 if (Monitor.TryEnter(lock1, TimeSpan.FromSeconds(5)))
37                 {
38                     WriteLine("Acquired a protected resource succesfully");
39                 }
40                 else
41                 {
42                     WriteLine("Timeout acquiring a resource!");
43                 }
44             }
45         }
46     }
47 }

5、運行該控制台應用程式,運行效果如下圖所示:

  此時,我們的控制台應用程式就避免了死鎖的發生。

十一、處理異常

   在這一小節中,我們講述如何線上程中正確地處理異常。正確地將try/catch塊放置線上程內部是非常重要的,因為線上程外部捕獲線程內部的異常通常是不可能的。

1、使用Visual Studio 2015創建一個新的控制台應用程式。

2、雙擊打開“Program.cs”文件,修改代碼如下所示:

 1 using System;
 2 using System.Threading;
 3 using static System.Console;
 4 using static System.Threading.Thread;
 5 
 6 namespace Recipe11
 7 {
 8     class Program
 9     {
10         static void BadFaultyThread()
11         {
12             WriteLine("Starting a faulty thread...");
13             Sleep(TimeSpan.FromSeconds(2));
14             throw new Exception("Boom!");
15         }
16 
17         static void FaultyThread()
18         {
19             try
20             {
21                 WriteLine("Starting a faulty thread...");
22                 Sleep(TimeSpan.FromSeconds(1));
23                 throw new Exception("Boom!");
24             }
25             catch(Exception ex)
26             {
27                 WriteLine($"Exception handled: {ex.Message}");
28             }
29         }
30 
31         static void Main(string[] args)
32         {
33             var t = new Thread(FaultyThread);
34             t.Start();
35             t.Join();
36 
37             try
38             {
39                 t = new Thread(BadFaultyThread);
40                 t.Start();
41             }
42             catch (Exception ex)
43             {
44                 WriteLine(ex.Message);
45                 WriteLine("We won't get here!");
46             }
47         }
48     }
49 }

3、運行該控制台應用程式,運行效果如下圖所示:

  在第10~15行代碼處,我們定義了一個名為“BadFaultyThread”的方法,在該方法中拋出一個異常,並且沒有使用try/catch塊捕獲該異常。

  在第17~29行代碼處,我們定義了一個名為“FaultyThread”的方法,在該方法中也拋出一個異常,但是我們使用了try/catch塊捕獲了該異常。

  在第33~35行代碼處,我們創建了一個線程,在該線程中執行了“FaultyThread”方法,我們可以看到在這個新創建的線程中,我們正確地捕獲了在“FaultyThread”方法中拋出的異常。

  在第37~46行代碼處,我們又新創建了一個線程,在該線程中執行了“BadFaultyThread”方法,並且在主線程中使用try/catch塊來捕獲在新創建的線程中拋出的異常,不幸的的是我們在主線程中無法捕獲在新線程中拋出的異常。

  由此可以看到,在一個線程中捕獲另一個線程中的異常通常是不可行的。

  至此,多線程(基礎篇)我們就講述到這兒,之後我們將講述線程同步相關的知識,敬請期待!

  源碼下載


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 異常與處理 C# 語言的異常處理功能可幫助您處理程式運行時出現的任何意外或異常情況。 異常處理使用 try、catch 和 finally 關鍵字嘗試某些操作,以處理失敗情況,儘管這些操作有可能失敗,但如果您確定需要這樣做,且希望在事後清理資源,就可以嘗試這樣做。 公共語言運行時 (CLR)、.NE ...
  • 本人經常與webservice打交道,特意寫了個小工具來調用Webservice方便測試,還有待進一步完善。使用方法如下 : 填寫完webservice的wsdl地址後點擊載入,將在方法那一側列出該服務所包含的方法,選中方法後在右側列出該方法所需參數,填完參數值後點擊調用在下方顯示結果,在標題欄顯示 ...
  • 方法不能跟變數一樣當參數傳遞,怎麼辦,C#定義了委托,就可以把方法當變數一樣傳遞了,為了簡單,匿名方法傳遞,省得再聲明方法了;再簡單,lambda表達式傳遞,比匿名方法更直觀。 public delegate int delegateArithmetic(int a, int b); //委托作為參 ...
  • 裝箱是將值類型轉換為 object 類型或由此值類型實現的任何介面類型的過程。 當 CLR 對值類型進行裝箱時,會將該值包裝到 System.Object 內部,再將後者存儲在托管堆上。 取消裝箱將從對象中提取值類型。 裝箱是隱式的;拆箱是顯式的。 裝箱和拆箱的概念是類型系統 C# 統一視圖的基礎, ...
  • 線程池的作用線程池,顧名思義,線程對象池。Task和TPL都有用到線程池,所以瞭解線程池的內幕有助於你寫出更好的程式。由於篇幅有限,在這裡我只講解以下核心概念: 線程池的大小 如何調用線程池添加任務 線程池如何執行任務 Threadpool也支持操控IOCP的線程,但在這裡我們不研究它,和task以 ...
  • 初學者,採用簡單三層架構,本來想用autofac依賴註入,只用sql server,沒打算遷移到別的資料庫,因此,簡單一點,就不用了。先做用戶管理模塊,登錄註冊等,客戶端cooke存儲,利用DESCryptoServiceProvider加密,源代碼下載地址:https://github.com/J ...
  • 最近悟出來一個道理,在這兒分享給大家:學歷代表你的過去,能力代表你的現在,學習代表你的將來。 十年河東十年河西,莫欺少年窮 學無止境,精益求精 C#洗牌演算法如下: 採用的是交換位置法,程式執行54次。效率還是頗高滴! @陳卧龍的博客 ...
  • 一、前言 在WPF編程中,有時候我們使用DataGrid會需要在一個DataColumn中既有TextBox,也要有ComboBox或者TextBlock等其他數據顯示樣式。 這個時候我們就需要DataGridTemplateColumn去自定義我們的Column樣式,通過數據類型去判斷該信息是以T ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...