引言:
進程(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