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
總結
以上是生活随笔為你收集整理的C# 多线程编程及其几种方式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: REST访问(RestTemplate)
- 下一篇: Java 面向对象 之 引用传递