轉載:https://www.cnblogs.com/xiashengwang/archive/2012/08/18/2645588.htmlhtml
無論咱們使用thread,threadPool,task,仍是APM異步,本質都是在使用多線程。對於新手來講,不太敢用多線程的緣由,就我我的的體驗來講,就是對多線程的異常捕獲方式或時機缺少了解,而一旦出現異常沒有捕獲,將會帶來難以發現的bug,進而形成系統崩潰。而多線程自己也不是一朝一夕就能學好的,必須不斷的去學習總結,因此我我的認爲你要用一種線程模型,首先要對它有足夠的瞭解,特別是對異常的捕獲。若是你沒有徹底的把握,最好在實際開發中謹慎的用多線程。編程
採用BeginXXX和EndXXX方法。關於異常的捕捉,對於剛調用BeginXXX拋出的異常,異步操做可能尚未進入隊列。這種異常通常能夠忽略。對於進入異步操做時發生的異常,會將錯誤碼放入IAsyncResult對象中,在咱們調用EndXXX方法時,會將這個錯誤碼轉換成一個恰當的Exception再次拋出。因此對於APM編程模型來講,咱們只用對EndXXX方法進行異常捕捉。僞代碼:多線程
Try { Result = someObj.EndXXX(IAsyncResult); } Catch(xxxException e) { //異常處理 }
注意事項:異步
1) 對於EndXXX方法的調用是必須的,不然可能會形成資源的泄漏,即便你可能不關心異步調用的返回結果,也要記住調用這個方法。異步編程
2) 只能調用一次EndXXX方法。函數
3) 調用EndXXX方法老是使用和BeginXXX時相同的對象。這裏辨別的是引用,引用不一樣就被視爲不一樣的對象。對於Delegate要補充一點,即便是相同簽名的委託,它們被編譯器編譯成具體的類,這些類的類名是不同的。學習
4) 不能取消異步I/O限制的異步操做。不要迷信這句話,他說的是I/O操做,是指的一個請求動做,若是咱們的是屢次請求,好比異步分塊上傳文件,是能夠作取消功能的。ui
5) FCL中有許多的I/O操做類都實現了APM。如派生自System.IO.Stream的類,Socket,Dns,WebRequest,還有SqlCommand等等。它們都提供了BeginXXX和EndXXX方法。spa
6) 能夠用APM來執行任何方法,咱們只須要定義一個與方法簽名一致的delegate,delegate編譯後會生成一個BeginInvoke和EndInvoke方法來支持APM操做。線程
Thread和ThreadPool發起異步的缺點:
1)沒有內建的機制知道任務什麼時候完成。
2)無法獲得任務的返回值。
Thread的開銷太大,儘可能用ThreadPool,除非你要顯示指定你的thread爲前臺線程或要對線程設置優先級,不然就不要用thread。
注意:線程池是由全部的AppDomain共享的。一個CLR維持一個線程池。
Task的引入,解決了上面的兩個問題。
1)Task可經過Wait()方法來等待任務的完成。這個方法是阻塞的。
2)經過Task.Result能夠獲得返回結果。在Result內部調用了Wait方法,因此查詢這個屬性是阻塞的。
3)對於任務函數的未處理異常,會被包裝成AggregateException異常拋出。可捕捉Wait()方法和Result屬性。經過AggregateException的InnerExceptions能夠進一步查詢具體的異常。
4)Task的靜態方法WaitAny和WaitAll能夠等待多個任務返回。一樣能夠捕捉這兩個方法的異常。
5)對於沒有調用Wait,Result,Exception來查詢未處理異常的狀況,例如:只調用了Task.Start方法。Task對象被回收時,Finalize方法會再次拋出這個異常終止進程。能夠向TaskScheduler.UnobservedTaskException事件註冊一個方法,來處理這類異常。經過UnobservedTaskExceptionEventArgs的SetObserved方法,能夠忽略掉這個異常,使進程不會終止。
6)構造Task時,能夠傳遞CancellationToken對象,以支持取消。若是是任務函數,經過調用CancellationToken.ThrowIfCancellationRequested 拋出的異常,類型是OperationCanceledException。若是任務函數沒有傳遞CancellationToken對象,那麼拋出的異常是TaskCanceledException,至關於任務級別的取消。
7) Task的ContinueWith方法能夠在第一個任務完成時開啓第二個任務。這個功能很強大,ContinueWith方法並不阻塞調用線程,它是異步的。咱們能夠在ContinueWith中寫一個事件回調方法,它能夠起到事件完成通知的做用。但它只能收到任務完成的通知,要實現任務進度的更新通知,到目前爲止,task依然作不到。
Task<int> t = new Task<int>(() => Sum(100)); t.Start(); t.ContinueWith(task => Console.WriteLine("result:" + task.Result), TaskContinuationOptions.OnlyOnRanToCompletion); t.ContinueWith(task => Console.WriteLine("canceled"), TaskContinuationOptions.OnlyOnCanceled); t.ContinueWith(task => Console.WriteLine("failed"), TaskContinuationOptions.OnlyOnFaulted);
對於上面的這一竄代碼,若是有未處理異常,一樣會形成進程終止。你一樣能夠用TaskScheduler.UnobservedTaskException事件註冊一個方法來處理。
8)Task能夠指定子任務,子任務沒有完成,父任務的ContinueTask也不會執行。關於異常和上面的處理方法同樣。由於這個也是不阻塞的,未處理異常暫時也只能在TaskScheduler.UnobservedTaskException裏處理。
Task<Int32[]> parent = new Task<int[]>(() => { Int32[] result = new Int32[3]; new Task<Int32>(() => result[0] = Sum(100), TaskCreationOptions.AttachedToParent).Start(); new Task<Int32>(() => result[1] = Sum(200), TaskCreationOptions.AttachedToParent).Start(); new Task<Int32>(() => result[2] = Sum(300), TaskCreationOptions.AttachedToParent).Start(); return result; }); parent.ContinueWith(parentTask => Array.ForEach(parentTask.Result, num => Console.WriteLine(num))); parent.Start();
9)TaskFactroy能夠簡化一組類似Task的建立工做。
Task parent = new Task(() => { CancellationTokenSource cts = new CancellationTokenSource(); TaskFactory<Int32> tf = new TaskFactory<Int32>(cts.Token, TaskCreationOptions.AttachedToParent, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); //create three child task var childTasks = new[]{ tf.StartNew(()=>Sum(cts.Token,100)), tf.StartNew(()=>Sum(cts.Token,200)), tf.StartNew(()=>Sum(cts.Token,Int32.MaxValue)) }; //when one failed,cancel the other for (int i = 0; i < childTasks.Length; i++) { childTasks[i].ContinueWith(task => cts.Cancel(), TaskContinuationOptions.OnlyOnFaulted); } //display the maxvalue tf.ContinueWhenAll( childTasks, completeTasks => completeTasks.Where( task => !task.IsCanceled && !task.IsFaulted).Max(t => t.Result), CancellationToken.None).ContinueWith(task => Console.WriteLine("the max is:" + task.Result)); }); //show exception parent.ContinueWith(p => { StringBuilder sb = new StringBuilder(); sb.AppendLine("error occours:"); foreach (var e in p.Exception.Flatten().InnerExceptions) { sb.AppendLine(e.Message); } Console.WriteLine(sb.ToString()); }, TaskContinuationOptions.OnlyOnFaulted); parent.Start();
1)CancellationTokenSource.Token方法返回CancellationToken,能夠將CancellationToken傳入咱們的工做方法,並查詢CancellationToken.IsCancellationRequested屬性來得到操做是否已取消。取消的狀況下,能夠結束工做方法。
2)通常在主線程調用取消方法。CancellationToeknSource.Cancel。
3)取消時能夠加入回調方法,經過CancellationToken.Register方法註冊。對於回調方法拋出的異常,能夠捕捉Cancel方法,異常會被包裝到AggregateException異常中,查詢InnerExceptions可的異常的詳細信息。
4)CancellationTokenSource的靜態方法CreateLinkedTokenSource能夠建立一個關聯的CreateLinkedTokenSource對象。任意其中的一個CreateLinkedTokenSource被取消,這個關聯的CreateLinkedTokenSource就會被取消。
分爲線程池任務調度器(thread pool task scheduler)和同步上下文任務調度器(synchroliazation context task scheduler)。其中同步上下文任務調度器能將全部的任務調度給UI線程,這對於更新界面的異步操做至關有用!默認的調度器是線程池任務調度器。
非UI線程更新UI界面會報錯,能夠用下面的方法,指定同步上下文任務調度器:
TaskScheduler syncSch = TaskScheduler.FromCurrentSynchronizationContext(); Task<int> t = new Task<int>(() => Sum(100)); //update UI with Synchronizationcontext t.ContinueWith(task => Text = task.Result.ToString(), syncSch); t.Start();
詳見個人另外一篇文章:
http://www.cnblogs.com/xiashengwang/archive/2012/08/18/2645541.html
這個類提供了For,Foreach,Invoke靜態方法。它內部封裝了Task類。主要用於並行計算。
private void ParallelTest2() { for (int i = 1; i < 5; i++) { Console.WriteLine(DoWork(i)); } //和上面的代碼等價,可是是多線程並行執行的,注意這裏的結束index不包含5 var plr = Parallel.For(1, 5, i => Console.WriteLine(DoWork(i))); } private int DoWork(int num) { int sum = 0; for (int i = 0; i <= num; i++) { sum += i; } return sum; }