C# 多線程編程及其幾種方式


引言:

進程(process):應用程序的實例要使用的資源的集合。每個進程被賦予了一個虛擬地址空間,確保在一個進程中使用的代碼和數據無法由另一個進程訪問。

線程(thread):程序中的一個執行流,每個線程都有自己的專有寄存器(棧指針、程序計數器等),但代碼區是共享的,及不同的線程可以執行相同的函數。

多線程編程優缺點,

優點:可以提高CPU利用率。

缺點:

1、線程越多占用內存越多;

2、多線程需要協調和管理,需要CPU時間跟蹤線程;

3、線程之間對共享資源會相互影響,必須解決競用共享資源問題;

4、線程太多會導致控制太復雜,可能造成很多BUG

一、用Thread類開啟線程

  關鍵字:前台線程,后台線程,線程優先級,線程休眠,線程阻塞。

  前台線程:主線程結束后,前台線程將繼續執行才結束。

  后台線程:主線程結束后,后台線程不管有沒有完成也結束線程

 1 class MultiThreadingApplication {
 2         static void Main(string[] args) {
 3             //Thread thread1 = new Thread(new ThreadStart(Test1));
 4             Thread thread1 = new Thread(Test1);//線程傳入無參數委托實例
 5             //Thread thread2 = new Thread(new ParameterizedThreadStart(Test2));//正常傳遞
 6             Thread thread2 = new Thread(Test2);//簡化傳遞
 7             thread1.Name = "線程1";
 8             thread1.IsBackground = true;//設為后台線程,主線成結束后台線程自動結束;前台線程在主線程結束后繼續執行才結束
 9             thread2.Name = "線程2";
10             thread1.Start();
11             thread2.Start("HelloWorld");
12             Thread thread3 = new Thread(() =>
13             {
14                 Console.WriteLine("線程3開始");
15                 Console.WriteLine("線程3阻塞5秒鍾");
16                 Thread.CurrentThread.Join(TimeSpan.FromSeconds(5));
17                 Console.WriteLine("{0}的執行方法",Thread.CurrentThread.Name);
18                 Console.WriteLine("線程3結束");
19             });
20             thread3.Name = "線程3";
21             thread3.Priority = ThreadPriority.Highest;//線程優先級枚舉設定,此時最高,在線程池中會優先開始
22             thread3.Start();
23             Console.WriteLine("Main()主函數線程結束");
24         }
25 
26         static void Test1() {
27             Console.WriteLine("線程1開始");
28             Console.WriteLine("線程1休眠2秒鍾");
29             Thread.Sleep(2000);
30             Console.WriteLine("{0}調用的無參數方法",Thread.CurrentThread.Name);
31             Console.WriteLine(Thread.CurrentThread.Name+"結束");
32         }
33 
34         static void Test2(object s) {
35             Console.WriteLine("線程2開始");
36             Console.WriteLine("{0}調用的有參數方法,方法的參數是:{1}", Thread.CurrentThread.Name, s);
37             Console.WriteLine(Thread.CurrentThread.Name + "結束");
38         }
39     }  

   Thread Join()使用,在兩個線程調用之間使用,阻塞當前線程,直到另一個線程結束。將兩個交替的線程合並為順序執行的線程。

比如在主線程中調用后台子線程的方法Join(),直到后台子線程執行完畢,主線程才繼續執行。

 1  static void Main(string[] args)
 2         {
 3             Console.WriteLine("主線程開始");
 4             var t = new Thread(() =>
 5             {
 6                 Console.WriteLine("后台子線程開始");
 7                 Thread.Sleep(3000);
 8                 Console.WriteLine("后台子線程");
 9                 Console.WriteLine("后台子線程結束");
10             });
11             t.IsBackground = true;
12             t.Start();
13             t.Join();//等待后台子線程執行完成,才繼續執行主線程,主線程在這里被阻塞。
14             //t.Join(TimeSpan.FromSeconds(5));
15             //t.Join(5000);//主線程阻塞等待后台子線程執行5秒鍾,然后繼續執行主線程,5秒后不管后台子線程是否執行完成。
16             //Thread.CurrentThread.Join();//死鎖情況,A線程和B線程為同一個線程,將一直阻塞。
17             Console.WriteLine("主線程結束");
18         }

 二、通過線程池類ThreadPool開啟線程

 1 static void Main(string[] args)
 2         {
 3             Console.WriteLine("主線程開始");
 4             ThreadPool.QueueUserWorkItem(DownLoadFile);
 5             //使用ThreadPool線程池開啟一個線程
 6             ThreadPool.QueueUserWorkItem((p) =>
 7             {
 8                 Console.WriteLine("是否線程池線程:{0}",Thread.CurrentThread.IsThreadPoolThread);
 9                 Console.WriteLine("開啟了一個線程,線程ID:{0}",Thread.CurrentThread.ManagedThreadId);
10             });
11             Thread.Sleep(5000);
12             Console.WriteLine("主線程結束");
13         }
14 
15         static void DownLoadFile(object state)
16         {
17             Console.WriteLine("開始下載...    線程ID:" + Thread.CurrentThread.ManagedThreadId);
18             Thread.Sleep(2000);
19             Console.WriteLine("下載完成!");
20         }

 三、Task或TaskFactory方式開啟,叫法不同了,任務並行編程

Task需要啟動任務執行,TaskFactory創建及開始執行

Task開啟的是后台線程

 1  static void Main(string[] args)
 2         {
 3             Console.WriteLine("主線程開始");
 4             //.net framework 4.0 TPL(Task Parallel Library, 簡稱TPL)任務並行庫方式,類似於線程處理方式,抽象級別更高
 5             //任務並行,一個或多個任務同時運行
 6             //系統資源的使用效率更高,可伸縮性更好
 7             //TPL提供了一組簡單豐富的 API,這些 API 支持等待、取消、繼續、可靠的異常處理、詳細狀態、自定義計划等功能。
 8             //降低多線程編碼和並行編程的復雜度,提升開發效率
 9             //使用Task開啟一個任務(其實也是一個線程)
10             Task task = new Task(new Action(() =>
11             {
12                 Console.WriteLine("是否線程池線程:{0}", Thread.CurrentThread.IsThreadPoolThread);
13                 Console.WriteLine("開啟了一個線程,線程ID:{0}", Thread.CurrentThread.ManagedThreadId);
14             }));
15             task.Start();
16 
17             Task.Factory.StartNew(() =>
18             {
19                 Console.WriteLine("是否線程池線程:{0}", Thread.CurrentThread.IsThreadPoolThread);
20                 Console.WriteLine("開啟了一個線程,線程ID:{0}", Thread.CurrentThread.ManagedThreadId);
21             });
22 
23             TaskFactory tf = new TaskFactory();
24             tf.StartNew(() =>
25             {
26                 Console.WriteLine("是否線程池線程:{0}", Thread.CurrentThread.IsThreadPoolThread);
27                 Console.WriteLine("開啟了一個線程,線程ID:{0}", Thread.CurrentThread.ManagedThreadId);
28             });29 
30             Thread.Sleep(2000);
31             Console.WriteLine("主線程結束");
32         }

 CancellationToken 傳入取消令牌,外部控制任務內部結束

 1  static void Main(string[] args)
 2         {
 3             Console.WriteLine("主線程開始");
 4             var cts = new CancellationTokenSource();
 5             var task = new Task<int>(()=> {
 6                 return TaskAction("task", 10, cts.Token);
 7             });
 8             task.Start();
 9             Console.WriteLine(task.Status);
10             cts.Cancel();
11             Console.WriteLine(task.Status);
12             task.Wait();
13             Thread.Sleep(2000);
14             Console.WriteLine(task.Status);
15             Console.WriteLine("task結果:{0}",task.Result);
16             Console.WriteLine("主線程結束");
17         }
18 
19         static int TaskAction(string name, int seconds, CancellationToken token) {
20             Console.WriteLine("Task:{0} is runing on Thread:{1}",name,Thread.CurrentThread.ManagedThreadId);
21             for (int i = 0; i < seconds; i++)
22             {
23                 Thread.Sleep(1000);
24                 if (token.IsCancellationRequested)
25                 {
26                     Console.WriteLine("請求取消令牌產生作用");
27                     return -1;
28                 }
29             }
30             return 42 * seconds;
31         }

 創建任務集合及返回結果

 1 static void Main(string[] args)
 2         {
 3             //Task<T>,Result等待任務調用完成得到結果,有Wait的作用
 4             var tasks = new List<Task<string>>() {
 5                 Task.Factory.StartNew(()=> {
 6                     return "task1";
 7                 }),
 8                 Task.Factory.StartNew(()=> {
 9                     return "task2";
10                 }),
11                 Task.Factory.StartNew(()=> {
12                     return "task3";
13                 }),
14             };
15 
16             foreach (var task in tasks)
17             {
18                 Console.WriteLine(task.Result);
19             }
20         }

異常捕獲

通過Catch捕獲Task的異常是AggregateException,一個被封裝的異常,需要通過InnerException訪問底層異常

推薦使用GetWaiter和GetResult方法訪問Task的結果,可以獲取到原始異常;

通過ContinueWith處理OnlyOnFaulted事件,捕獲的異常也是一個被封裝的異常,需要通過InnerException訪問底層異常

 1  static void Main(string[] args)
 2         {
 3             Console.WriteLine("主線程開始");
 4             Task<int> task = null;
 5             try
 6             {
 7                 task = Task.Run(() => TaskExceptionAction("Task1", 2));
 8                 var result = task.Result;
 9                 Console.WriteLine(result);
10             }
11             catch (Exception e)
12             {
13                 Console.WriteLine("Task 1 Exception,type:" + e.GetType() + ",Message:" + e.Message);
14             }
15             Console.WriteLine("==============================");
16             try
17             {
18                 task = Task.Run(() => TaskExceptionAction("Task2", 2));
19                 var result = task.GetAwaiter().GetResult();
20                 Console.WriteLine(result);
21             }
22             catch (Exception e)
23             {
24                 Console.WriteLine("Task 2 Exception,type:" + e.GetType() + ",Message:" + e.Message);
25             }
26 
27             var task3 = new Task<int>(() =>
28             {
29                 return TaskExceptionAction("task3", 2);
30             });
31             var continueTask = Task.WhenAll(task3);
32             continueTask.ContinueWith(t =>
33             {
34                 Console.WriteLine("Task 3 Exception,type:" + t.Exception.GetType() + ",Message:" + t.Exception.Message);
35             }, TaskContinuationOptions.OnlyOnFaulted);
36             task3.Start();
37             task3.Wait();
38             Console.WriteLine("主線程結束");
39         }
40 
41         static int TaskExceptionAction(string name, int seconds)
42         {
43             Console.WriteLine("任務:{0} 在線程:{1}上運行", name, Thread.CurrentThread.ManagedThreadId);
44             Thread.Sleep(seconds * 1000);
45             throw new Exception("Error");
46         }

 多任務的串行化

 1  static void Main(string[] args)
 2         {
 3             Console.WriteLine("主線程開始");
 4             var queryTask = new Task<string>(() =>
 5             {
 6                 Console.WriteLine("Start queryTask!");
 7                 return "QueryResult";
 8             });
 9             var analyzeTask = queryTask.ContinueWith((queryResult) =>
10             {
11                 Console.WriteLine("Start AnalyzeTask!");
12                 return "Analyzed Data:" + queryResult.Result;
13             });
14             var reportTask = analyzeTask.ContinueWith((analyzeResult) =>
15             {
16                 Console.WriteLine("Start ReportTask!");
17                 return "Reporting Data:" + analyzeResult.Result;
18             });
19             queryTask.Start();
20             Console.WriteLine(reportTask.Result);
21             Console.WriteLine("主線程結束");
22         }

 除非所有子任務(子任務的子任務)結束運行,否則創建任務(父任務)不會認為已經結束

父任務異常不會影響子任務執行

子任務異常不會影響父任務執行

 1  static void Main(string[] args)
 2         {
 3             Console.WriteLine("主線程開始");
 4             var parentTask = Task.Factory.StartNew(() =>
 5             {
 6                 Console.WriteLine("創建一個父任務");
 7                 var subTask = Task.Factory.StartNew(() =>
 8                 {
 9                     Console.WriteLine("創建一個子任務");
10                     throw new Exception("subTask Error");
11                 }, TaskCreationOptions.AttachedToParent);
12                 Console.WriteLine("父任務結束");
13             });
14             Thread.Sleep(5000);
15             Console.WriteLine("主線程結束");
16         }

 四、異步委托開啟多線程

//TODO

部分內容來自:https://www.cnblogs.com/tianqing/p/6970331.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM