任務(task)

任務概述

線程(Thread是建立併發的底層工具,所以有必定的侷限性(不易獲得返回值(必須經過建立共享域);異常的捕獲和處理也麻煩;同時線程執行完畢後沒法再次開啓該線程),這些侷限性會下降性能同時影響併發性的實現(不容易組合較小的併發操做實現較大的併發操做,會增長手工同步處理(加鎖,發送信號)的依賴,容易出現問題)。html

線程池的(ThreadPoolQueueUserWorkItem方法很容發起一次異步的計算限制操做。但這個技術一樣有着許多限制,最大的問題是沒有內建的機制讓你知道操做在何時完成,也沒有機制在操做完成時得到返回值。編程

Task類能夠解決上述全部的問題。併發

任務(Task表示一個經過或不經過線程實現的併發操做,任務是可組合的,使用延續(continuation)可將它們串聯在一塊兒,它們可使用線程池減小啓動延遲,可以使用回調方法避免多個線程同時等待I/O密集操做。異步

 

基礎任務(Task)

微軟在.NET 4.0 引入任務(Task的概念。經過System.Threading.Tasks命名空間使用任務。它是在ThreadPool的基礎上進行封裝的。Task默認都是使用池化線程,它們都是後臺線程,這意味着主線程結束時其它任務也會隨之中止。async

啓動一個任務有多種方式,如如下示例:ide

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Console.WriteLine("主線程Id:{0}", Thread.CurrentThread.ManagedThreadId);
 6             int workerThreadsCount, completionPortThreadsCount;
 7             ThreadPool.GetAvailableThreads(out workerThreadsCount, out completionPortThreadsCount);
 8             Console.WriteLine("剩餘工做線程數:{0},剩餘IO線程數{1}", workerThreadsCount, completionPortThreadsCount);
 9             //第一種:實例化方式Start啓動
10             {
11                 Task task = new Task(() =>
12                 {
13                     Test("one-ok");
14                 });
15                 task.Start();
16             }
17             //第二種:經過Task類靜態方法Run方式進行啓動
18             {
19                 Task.Run(() =>
20                 {
21                     Test("two-ok");
22                 });
23             }
24             //第三種:經過TaskFactory的StartNew方法啓動
25             {
26                 TaskFactory taskFactory = new TaskFactory();
27                 taskFactory.StartNew(() =>
28                 {
29                     Test("three-ok");
30                 });
31             }
32             //第四種:.經過Task.Factory進行啓動
33             {
34                 Task taskStarNew = Task.Factory.StartNew(() =>
35                 {
36                     Test("four-ok");
37                 });
38             }
39             //第五種:經過Task對象的RunSynchronously方法啓動(同步,由主線程執行,會卡主線程)
40             {
41                 Task taskRunSync = new Task(() =>
42                 {
43                     Console.WriteLine("線程Id:{0},執行方法:five-ok", Thread.CurrentThread.ManagedThreadId);
44                 });
45                 taskRunSync.RunSynchronously();
46             }
47             Thread.Sleep(1000);
48             ThreadPool.GetAvailableThreads(out workerThreadsCount, out completionPortThreadsCount);
49             Console.WriteLine("剩餘工做線程數:{0},剩餘IO線程數{1}", workerThreadsCount, completionPortThreadsCount);
50             Console.ReadKey();
51         }
52         static void Test(string o)
53         {
54             Thread.Sleep(2000);
55             Console.WriteLine("線程Id:{0},執行方法:{1}", Thread.CurrentThread.ManagedThreadId, o);
56         }
57         /*
58          * 做者:Jonins
59          * 出處:http://www.cnblogs.com/jonins/
60          */
61     }

執行結果:函數

上面示例中除去使用RunSynchronously方法啓動的是同步任務(由啓用的線程執行任務)外,其它幾種方式內部都由線程池內的工做者線程處理。工具

說明性能

1.事實上Task.Factory類型自己就是TaskFactory(任務工廠),而Task.Run(在.NET4.5引入,4.0版本調用的是後者)是Task.Factory.StartNew的簡寫法,是後者的重載版本,更靈活簡單些。測試

2.調用靜態Run方法會自動建立Task對象並當即調用Start

3.如Task.Run等方式啓動任務並無調用Start,由於它建立的是「熱」任務,相反「冷」任務的建立是經過Task構造函數。

 

返回值(Task<TResult>)&狀態(Status)

Task有一個泛型子類Task<TResult>,它容許任務返回一個值。調用Task.Run,傳入一個Func<Tresult>代理或兼容的Lambda表達式,而後查詢Result屬性得到結果。若是任務沒有完成,那麼訪問Result屬性會阻塞當前線程,直至任務完成

1     public static Task<TResult> Run<TResult>(Func<TResult> function);

而任務的Status屬性可用於跟蹤任務的執行狀態,以下所示:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Task<int> task = Task.Run(() =>
 6             {
 7                 int total = 0;
 8                 for (int i = 0; i <= 100; i++)
 9                 {
10                     total += i;
11                 }
12                 Thread.Sleep(2000);
13                 return total;
14             });
15             Console.WriteLine("任務狀態:{0}",task.Status);
16             Thread.Sleep(1000);
17             Console.WriteLine("任務狀態:{0}", task.Status);
18             int totalCount = task.Result;//若是任務沒有完成,則阻塞
19             Console.WriteLine("任務狀態:{0}", task.Status);
20             Console.WriteLine("總數爲:{0}",totalCount);
21             Console.ReadKey();
22         }
23     }

執行以下:

 

Reulst屬性內部會調用Wait(等待);

任務的Status屬性是一個TaskStatus枚舉類型:

1  public TaskStatus Status { get; }

說明以下:

枚舉值 說明
Canceled

任務已經過對其自身的 CancellationToken 引起 OperationCanceledException 對取消進行了確認,此時該標記處於已發送信號狀態;

或者在該任務開始執行以前,已向該任務的 CancellationToken 發出了信號。

Created 該任務已初始化,但還沒有被計劃。
Faulted 因爲未處理異常的緣由而完成的任務。
RanToCompletion 已完成執行的任務。
Running 任務正在運行,還沒有完成。
WaitingForActivation 該任務正在等待 .NET Framework 基礎結構在內部將其激活並進行計劃。
WaitingForChildrenToComplete 該任務已完成執行,正在隱式等待附加的子任務完成。
WaitingToRun 該任務已被計劃執行,但還沒有開始執行。

 

任務集合返回值(WhenAll&WhenAny)

 Task中有很是方便的對並行運行的任務集合獲取返回值的方式,好比WhenAllWhenAny

1.WhenAll

WhenAll等待提供的全部 Task 對象完成執行過程(全部任務所有完成)。

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             List<Task<int>> taskList = new List<Task<int>>();//聲明一個任務集合
 6             TaskFactory taskFactory = new TaskFactory();
 7             for (int i = 0; i < 5; i++)
 8             {
 9                 int total = i;
10                 Task<int> task = taskFactory.StartNew(() => Test(total));
11                 taskList.Add(task);//將任務放進集合中
12             }
13             Console.WriteLine("主線程Id:{0},繼續執行A.....", Thread.CurrentThread.ManagedThreadId);
14             Task<int[]> taskReulstList = Task.WhenAll(taskList);//建立一個任務,該任務將集合中的全部 Task 對象都完成時完成
15             for (int i = 0; i < taskReulstList.Result.Length; i++)//這裏調用了Result,因此會阻塞線程,等待集合內全部任務所有完成
16             {
17                 Console.WriteLine("返回值:{0}", taskReulstList.Result[i]);//遍歷任務集合內Task返回的值
18             }
19             Console.WriteLine("主線程Id:{0},繼續執行B.....", Thread.CurrentThread.ManagedThreadId);
20             Console.ReadKey();
21         }
22         private static int Test(int o)
23         {
24             Console.WriteLine("線程Id:{0},Task執行成功,參數爲:{1}", Thread.CurrentThread.ManagedThreadId, o);
25             Thread.Sleep(500 * o);
26             return o;
27         }
28     }

執行結果:

2.WhenAny

WhenAny:等待提供的任一 Task 對象完成執行過程(只要有一個任務完成)。

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             List<Task<int>> taskList = new List<Task<int>>();//聲明一個任務集合
 6             TaskFactory taskFactory = new TaskFactory();
 7             for (int i = 0; i < 5; i++)
 8             {
 9                 int total = i;
10                 Task<int> task = taskFactory.StartNew(() => Test(total));
11                 taskList.Add(task);//將任務放進集合中
12             }
13             Console.WriteLine("主線程Id:{0},繼續執行A.....", Thread.CurrentThread.ManagedThreadId);
14             Task<Task<int>> taskReulstList = Task.WhenAny(taskList);//建立一個任務,該任務將在集合中的任意 Task 對象完成時完成
15             Console.WriteLine("返回值:{0}", taskReulstList.Result.Result);//獲得任務集合內最早完成的任務的返回值
16             Console.WriteLine("主線程Id:{0},繼續執行B.....", Thread.CurrentThread.ManagedThreadId);
17             Console.ReadKey();
18         }
19         private static int Test(int o)
20         {
21             Console.WriteLine("線程Id:{0},Task執行成功,參數爲:{1}", Thread.CurrentThread.ManagedThreadId, o);
22             Thread.Sleep(500 * o);
23             return o;
24         }
25     }

執行結果(這裏返回值確定會是0,由於休眠最短):

 

等待(Wait)&執行方式(TaskCreationOptions)

1.任務等待(Wait)

調用任務的Wait方法能夠阻塞任務直至任務完成,相似於線程的join

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Task task = Task.Run(() =>
 6             {
 7                 Console.WriteLine("線程執行Begin");
 8                 Thread.Sleep(2000);
 9                 Console.WriteLine("線程執行End");
10             });
11             Console.WriteLine("任務是否完成:{0}", task.IsCompleted);
12             task.Wait();//阻塞,直至任務完成
13             Console.WriteLine("任務是否完成:{0}", task.IsCompleted);
14             Console.ReadKey();
15         }
16     }

執行以下:

注意

線程調用Wait方法時,系統檢測線程要等待的Task是否已經開始執行。若是是線程則會阻塞直到Task運行結束爲止。但若是Task尚未開始執行任務,系統可能(取決於TaskScheduler)使用調用Wait的線程來執行Task,這種狀況下調用Wait的線程不會阻塞,它會執行Task並當即返回。好處在於沒有線程會被阻塞,因此減小了資源佔用。很差的地方在於加入線程在調用Wait前已經得到了一個線程同步鎖,而Task試圖獲取同一個鎖,就會形成死鎖的線程。

2.任務執行方式(TaskCreationOptions)

咱們知道爲了建立一個Task,須要調用構造函數並傳遞一個ActionAction<object>委託,若是傳遞的是期待一個Object的方法,還必須向Task的構造函數穿都要傳給操做的實參。還能夠選擇向構造器傳遞一些TaskCreationOptions標記來控制Task的執行方式。

 TaskCreationOptions爲枚舉類型

枚舉值 說明
None 默認。
PreferFairness 儘量公平的方式安排任務,即先進先執行。
LongRunning 指定任務將是長時間運行的,會新建線程執行,不會使用池化線程。
AttachedToParent 指定將任務附加到任務層次結構中的某個父級
DenyChildAttach 任務試圖和這個父任務鏈接將拋出一個InvalidOperationException
HideScheduler 強迫子任務使用默認調度而非父級任務調度

在默認狀況下,Task內部是運行在池化線程上,這種線程會很是適合執行短計算密集做業。若是要執行長阻塞操做,則要避免使用池化線程。

在池化線程上運行一個長任務問題不大,可是若是要同時運行多個長任務(特別是會阻塞的任務),則會對性能產生影響。最好使用:TaskCreationOptions.LongRunning

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             int workerThreadsCount, completionPortThreadsCount;
 6             ThreadPool.GetAvailableThreads(out workerThreadsCount, out completionPortThreadsCount);
 7             Console.WriteLine("剩餘工做線程數:{0},剩餘IO線程數{1},主線程Id:{2}", workerThreadsCount, completionPortThreadsCount, Thread.CurrentThread.ManagedThreadId);
 8             Task task = Task.Factory.StartNew(() =>
 9             {
10                 Console.WriteLine("長任務執行,線程Id:{0}", Thread.CurrentThread.ManagedThreadId);
11                 Thread.Sleep(2000);
12             }, TaskCreationOptions.LongRunning);
13             Thread.Sleep(1000);
14             ThreadPool.GetAvailableThreads(out workerThreadsCount, out completionPortThreadsCount);
15             Console.WriteLine("剩餘工做線程數:{0},剩餘IO線程數{1},主線程Id:{2}", workerThreadsCount, completionPortThreadsCount, Thread.CurrentThread.ManagedThreadId);
16             Console.ReadKey();
17         }
18     }

執行結果以下:

注意

若是使運行I/O密集任務,則可使用TaskCompletionSource和異步函數(asynchronous functions),經過回調(延續)實現併發性,而是不經過線程實現。

若是使運行計算密集性任務,則可使用一個生產者/消費者隊列,控制這些任務的併發數量,避免出現線程和進程阻塞的問題。

 

延續(continuation)&延續選項(TaskContinuationOptions)

延續(continuation)會告訴任務在完成後繼續執行下面的操做。延續一般由一個回調方法實現,它會在操做完成以後執行一次。給一個任務附加延續的方法有兩種

1.GetAwaiter

任務的方法GetAwaiter是Framework 4.5新增長的,而C# 5.0的異步功能使用了這種方法,所以它很是重要。給一個任務附加延續以下:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Task<int> task = Task.Run(() =>
 6              {
 7                  int total = 0;
 8                  for (int i = 0; i <= 100; i++)
 9                  {
10                      total += i;
11                  }
12                  Thread.Sleep(2000);
13                  return total;
14              });
15             var awaiter = task.GetAwaiter();
16             awaiter.OnCompleted(() =>
17             {
18                 int result = awaiter.GetResult();//在延續中獲取Task的執行結果 19                 Console.WriteLine(result);
20             });
21             Console.ReadKey();
22         }
23     }

執行結果控制檯會打印:5050。

調用GetAwaiter會返回一個等待者(awaiter)對象,它會讓先導(antecedent)任務在任務完成(或出錯)以後執行一個代理。已經完成的任務也能夠附加一個延續,這事延續會立刻執行。

注意

1.等待者(awaiter)能夠是任意對象,但必須包含特定的兩個方法和一個Boolean類型屬性。

1   public struct TaskAwaiter<TResult> : ICriticalNotifyCompletion, INotifyCompletion
2     {
3         public bool IsCompleted { get; }
4         public TResult GetResult();
5         public void OnCompleted(Action continuation);
6     }

2.先導任務出現錯誤,那麼當延續代碼調用awaiter.GetResult()時就會從新拋出異常。咱們能夠須要調用GetResult,而是直接訪問先導任務的Result屬性(task.Result)。

GetResult的好處是,當先導任務出現錯誤時,異常能夠直接拋出而不封裝在AggregateException中。

3.若是出現同步上下文,那麼會自動捕捉它,而後延續提交到這個上下文中。在無需同步上下文的狀況下一般不採用這種方法,使用ConfigureAwait代替它。它一般會使延續運行在先導任務所在的線程上,從而避免沒必要要的過載。

1    var awaiter = task.ConfigureAwait(false).GetAwaiter();

2.ContinueWith

另外一種附加延續的方法是調用任務的ContinueWith方法:

 1         static void Main(string[] args)
 2         {
 3             Task<int> task = Task.Run(() =>
 4             {
 5                 int total = 0;
 6                 for (int i = 0; i <= 100; i++)
 7                 {
 8                     total += i;
 9                 }
10                 Thread.Sleep(2000);
11                 return total;
12             });
13             task.ContinueWith(continuationAction =>
14             {
15                 int result = continuationAction.Result;
16                 Console.WriteLine(result);
17             });
18             Console.ReadKey();
19         }

ContinueWith自己會返回一個Task,它很是適用於添加更多的延續。而後若是任務出現錯誤,咱們必須直接處理AggregateException。

若是想讓延續運行在統一個線程上,必須指定 TaskContinuationOptions.ExecuteSynchronously;不然它會彈回線程池。ContinueWith特別適用於並行編程場景。

3.延續選項(TaskContinuationOptions)

在使用ContinueWith時能夠指定任務的延續選項即TaskContinuationOptions,它的前六個枚舉類型與以前說的TaskCreationOptions枚舉提供的標誌徹底同樣,補充後續幾個枚舉值:

枚舉值 說明
LazyCancellation 除非先導任務完成,不然禁止延續任務完成(取消)。
NotOnRanToCompletion 指定不該在延續任務前面的任務已完成運行的狀況下安排延續任務。
NotOnFaulted 指定不該在延續任務前面的任務引起了未處理異常的狀況下安排延續任務。
NotOnCanceled 指定不該在延續任務前面的任務已取消的狀況下安排延續任務。 
OnlyOnCanceled 指定只應在延續前面的任務已取消的狀況下安排延續任務。
OnlyOnFaulted 指定只有在延續任務前面的任務引起了未處理異常的狀況下才應安排延續任務。
OnlyOnRanToCompletion 指定只有在延續任務前面的任務引起了未處理異常的狀況下才應安排延續任務。
ExecuteSynchronously 指定但願由先導任務的線程執行,先導任務完成後線程繼續執行延續任務。

 

ExecuteSynchronously是指同步執行,兩個任務都在同一個=線程一前一後的執行。

ContinueWith結合TaskContinuationOptions使用的示例:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Task<int> task = Task.Run(() =>
 6             {
 7                 int total = 0;
 8                 for (int i = 0; i <= 100; i++)
 9                 {
10                     total += i;
11                 }
12                 if (total == 5050)
13                 {
14                     throw new Exception("錯誤");//這段代碼能夠註釋或開啓,用於測試
15                 }
16                 return total;
17             });
18             //指定先導任務無報錯的延續任務
19             task.ContinueWith(continuationAction =>
20             {
21                 int result = continuationAction.Result;
22                 Console.WriteLine(result);
23             }, TaskContinuationOptions.NotOnFaulted);
24             //指定先導任務報錯時的延續任務
25             task.ContinueWith(continuationAction =>
26             {
27                 foreach (Exception ex in continuationAction.Exception.InnerExceptions)//有關AggregateException異常處理後續討論 28                 {
29                     Console.WriteLine(ex.Message);
30                 }
31             }, TaskContinuationOptions.OnlyOnFaulted);
32             Console.ReadKey();
33         }
34     }

執行結果會打印:報錯,若是註釋掉拋出異常的代碼則會打印5050。

 

TaskCompletionSource

另外一種建立任務的方法是使用TaskCompletionSource。它容許建立一個任務,並能夠任務分發給使用者,而且這些使用者可使用該任務的任何成員。它的實現原理是經過一個能夠手動操做的「附屬」任務,用於指示操做完成或出錯的時間。

TaskCompletionSource的真正做用是建立一個不綁定線程的任務(手動控制任務工做流,可使你把建立任務和完成任務分開)

這種方法很是適合I/O密集做業:能夠利用全部任務的優勢(它們可以生成返回值、異常和延續),但不會在操做執行期間阻塞線程。

例如,假設一個任務須要等待2秒,而後返回10,咱們的方法會返回在一個2秒後完成的任務,經過給任務附加一個延續就能夠在不阻塞任何線程的前提下打印這個結果,以下:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             var awaiter = Demo(2000).GetAwaiter();//獲得任務經過延續輸出返回值
 6             awaiter.OnCompleted(() =>
 7             {
 8                 Console.WriteLine(awaiter.GetResult());
 9             });
10             Console.WriteLine("主線程繼續執行....");
11             Console.ReadKey();
12         }
13         static Task<int> Demo(int millis)
14         {
15             //建立一個任務完成源
16             TaskCompletionSource<int> taskCompletionSource = new TaskCompletionSource<int>();
17             var timer = new System.Timers.Timer(millis) { AutoReset = false };
18             timer.Elapsed += delegate
19             {
20                 timer.Dispose(); taskCompletionSource.SetResult(10);//寫入返回值
21             };
22             timer.Start();
23             return taskCompletionSource.Task;//返回任務
24         }
25     }

執行結果:

注意:若是屢次調用SetResultSetExceptionSetCanceled,它們會拋出異常,而TryXXX會返回false。

 

任務取消(CancellationTokenSource)

一些狀況下,後臺任務可能運行很長時間,取消任務就很是有用了。.NET提供了一種標準的任務取消機制可用於基於任務的異步模式

取消基於CancellationTokenSource類,該類可用於發送取消請求。請求發送給引用CancellationToken類的任務,其中CancellationToken類與CancellationTokenSource類相關聯。

使用示例以下:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             //構造函數 指定延遲2秒後自動取消任務
 6             CancellationTokenSource source = new CancellationTokenSource(2000);
 7             //註冊一個任務取消後執行的委託
 8             source.Token.Register(() =>
 9             {
10                 Console.WriteLine("線程Id:{0} 任務被取消後的業務邏輯正在運行", Thread.CurrentThread.ManagedThreadId);
11             });
12             //啓動任務,將取消標記源帶入參數
13             Task.Run(() =>
14             {
15                 while (!source.IsCancellationRequested)//IsCancellationRequested爲True時取消任務 16                 {
17                     Thread.Sleep(100);
18                     Console.WriteLine("線程Id:{0} 任務正在運行", Thread.CurrentThread.ManagedThreadId);
19                 }
20             }, source.Token);
21             //主線程掛起2秒後手動取消任務
22             {
23                 //Thread.Sleep(2000);
24                 //source.Cancel();//手動取消任務
25             }
26             //主線程不阻塞,2秒後自動取消任務
27             {
28                 source.CancelAfter(2000);
29             }
30             Console.ReadKey();
31         }
32     }

執行結果:

根據Register方法綁定任務取消後的委託

1   public CancellationTokenRegistration Register(Action callback);
2   public CancellationTokenRegistration Register(Action callback, bool useSynchronizationContext);
3   public CancellationTokenRegistration Register(Action<object> callback, object state);
4   public CancellationTokenRegistration Register(Action<object> callback, object state, bool useSynchronizationContext);

手動取消任務Cancel方法

自動取消任務

1.CancelAfter方法後面能夠帶入參數指定延遲多少後時間取消任務。

1   public void CancelAfter(TimeSpan delay);
2   public void CancelAfter(int millisecondsDelay);

2.CancellationTokenSource構造函數能夠帶入參數指定延遲多少時間後取消任務。

1   public CancellationTokenSource(TimeSpan delay);
2   public CancellationTokenSource(int millisecondsDelay);

任務綁定CancellationTokenSource對象,在Task源碼中能夠帶入CancellationToken對象的啓動任務方式均可以綁定CancellationTokenSource

 

異步等待 (Task.Delay)

 異步等待很是實用,所以它成爲Task類的一個靜態方法

 經常使用的使用方式有2種,以下:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             //第1種
 6             {
 7                 Task.Delay(2000).ContinueWith((o) =>
 8                 {
 9                     Console.WriteLine("線程Id:{0},異步等待2秒後執行的邏輯", Thread.CurrentThread.ManagedThreadId);
10                 });
11             }
12             //第2種
13             {
14                 Task.Delay(3000).GetAwaiter().OnCompleted(() =>
15                 {
16                     Console.WriteLine("線程Id:{0},異步等待3秒後執行的邏輯", Thread.CurrentThread.ManagedThreadId);
17                 });
18             }
19             Console.WriteLine("主線程Id:{0},繼續執行", Thread.CurrentThread.ManagedThreadId);
20             Console.ReadKey();
21         }
22     }

執行結果以下:

Task.DelayThread.Sleep的異步版本。而它們的區別以下(引自 禪道 ):

1.Thread.Sleep 是同步延遲,Task.Delay異步延遲。

2.Thread.Sleep 會阻塞線程,Task.Delay不會。

3.Thread.Sleep不能取消,Task.Delay能夠。

4. Task.Delay() 比 Thread.Sleep() 消耗更多的資源,可是Task.Delay()可用於爲方法返回Task類型;或者根據CancellationToken取消標記動態取消等待。

5. Task.Delay() 實質建立一個運行給定時間的任務, Thread.Sleep() 使當前線程休眠給定時間。

 

異常(AggregateException)

與線程不一樣,任務能夠隨時拋出異常。因此,若是任務中的代碼拋出一個未處理異常,那麼這個異常會自動傳遞到調用Wait()Task<TResult>Result屬性的代碼上。
任務的異常將會自動捕獲並拋給調用者。爲確保報告全部的異常,CLR會將異常封裝在AggregateException容器中,該容器公開的InnerExceptions屬性中包含全部捕獲的異常,從而更適合並行編程。

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             try
 6             {
 7                 Task.Run(() =>
 8                 {
 9                     throw new Exception("錯誤");
10                 }).Wait();
11             }
12             catch (AggregateException axe)
13             {
14                 foreach (var item in axe.InnerExceptions)
15                 {
16                     Console.WriteLine(item.Message);
17                 }
18             }
19             Console.ReadKey();
20         }
21     }

上述示例控制檯會顯示:錯誤

注意

使用TaskIsFaultedIsCanceled屬性,就能夠不從新拋出異常而檢測出錯的任務。
1.IsFaultedIsCanceled都返回False,表示沒有錯誤發生。
2.IsCanceledTrue,則任務拋出了OperationCanceledOperation(取消線程正在執行的操做時在線程中拋出的異常)。
3.IsFaultedTrue,則任務拋出另外一種異常,而Exception屬性包含了該錯誤。

1.Flatten

當子任務拋出異常時,經過調用Flatten方法,能夠消除任意層次的嵌套以簡化異常處理。

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             var parent = Task.Factory.StartNew(() =>
 6             {
 7                 int[] numbers = { 0 };
 8                 var childFactory = new TaskFactory(TaskCreationOptions.AttachedToParent, TaskContinuationOptions.None);
 9                 childFactory.StartNew(() => 10 / numbers[0]);//除零
10                 childFactory.StartNew(() => numbers[1]);//超出索引範圍
11                 childFactory.StartNew(() => throw null);//空引用
12             });
13             try
14             {
15                 parent.Wait();
16             }
17             catch (AggregateException axe)
18             {
19                 foreach (var item in axe.Flatten().InnerExceptions)
20                 {
21                     Console.WriteLine(item.Message);
22                 }
23             }
24             Console.ReadKey();
25         }
26     }

2.Handle

 若是須要只捕獲特定類型異常,並重拋其它類型的異常,Handle方法爲此提供了一種快捷方式。

Handle接受一個predicate(異常斷言),並在每一個內部異常上運行此斷言。

1 public void Handle(Func<Exception, bool> predicate);

若是斷言返回True,它認爲該異常是「已處理」,當全部異常過濾以後:

1.若是全部異常是已處理的,異常不會拋出。

2.若是存在異常未處理,就會構造一個新的AggregateException對象來包含這些異常並拋出。

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             var parent = Task.Factory.StartNew(() =>
 6             {
 7                 int[] numbers = { 0 };
 8                 var childFactory = new TaskFactory(TaskCreationOptions.AttachedToParent, TaskContinuationOptions.None);
 9                 childFactory.StartNew(() => 10 / numbers[0]);//除零
10                 childFactory.StartNew(() => numbers[1]);//超出索引範圍
11                 childFactory.StartNew(() => throw null);//空引用
12             });
13             try
14             {
15                 try
16                 {
17                     parent.Wait();
18                 }
19                 catch (AggregateException axe)
20                 {
21                     axe.Flatten().Handle(ex =>
22                     {
23                         if (ex is DivideByZeroException)
24                         {
25                             Console.WriteLine("除零-錯誤處理完畢");
26                             return true;
27                         }
28                         if (ex is IndexOutOfRangeException)
29                         {
30                             Console.WriteLine("超出索引範圍-錯誤處理完畢");
31                             return true;
32                         }
33                         return false;//全部其它 異常從新拋出
34                     });
35 
36                 }
37             }
38             catch (AggregateException axe)
39             {
40                 foreach (var item in axe.InnerExceptions)//捕獲從新拋出的異常
41                 {
42                     Console.WriteLine(item.Message);
43                 }
44             }
45             Console.ReadKey();
46         }
47     }

執行結果:

 

 結語

1.async和await這兩個關鍵字下篇記錄。

2.任務調度器(TaskScheduler)是Task之因此如此靈活的本質,咱們常說Task是在ThreadPool上更升級化的封裝,其實很大程度上歸功於這個對象,考慮下篇要不要說一下,但其實我看的都頭疼...

3.Task類包含不少的重載,最好F12跳到Task內熟悉下結構。

 

參考文獻 

CLR via C#(第4版) Jeffrey Richter

C#高級編程(第10版) C# 6 & .NET Core 1.0   Christian Nagel  

果殼中的C# C#5.0權威指南  Joseph Albahari

C#併發編程 經典實例  Stephen Cleary

...

相關文章
相關標籤/搜索