本示例學習如何實現基於Task的非同步操作進行取消流程,以及在任務真正運行前如何知道任務已經被取消。 我們學習如何在task中拋出不同情況的異常,以及如何獲取這些異常信息。 ...
六、 實現取消選項
本示例學習如何實現基於Task的非同步操作進行取消流程,以及在任務真正運行前如何知道任務已經被取消。
1.代碼如下:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ThreadTPLDemo { class Program { static void Main(string[] args) { Console.WriteLine(" 將Task中實現取消操作。。。"); var cts =new CancellationTokenSource(); var task1 = new Task<int>(() => RunTask("任務 1",10, cts.Token),cts.Token); Console.WriteLine(" ——task1 狀態—{0}", task1.Status); cts.Cancel(); Console.WriteLine(" ——取消——task1 狀態—{0}—",task1.Status); Console.WriteLine(" ——task1 在出錯之前 取消了操作—"); //task1.Start(); cts = new CancellationTokenSource(); var task2 = new Task<int>(() => RunTask("任務 2", 10, cts.Token),cts.Token); task2.Start(); for (int i = 0; i < 5; i++) { Thread.Sleep(500); Console.WriteLine(" ——task2 狀態—{0}", task2.Status); } cts.Cancel(); for (int i = 0; i < 5; i++) { Thread.Sleep(500); Console.WriteLine(" ——task2 狀態—{0}", task2.Status); } Console.WriteLine(" ——任務Task 運行結果—{0}", task2.Result); Thread.Sleep(2000); Console.Read(); } private static int RunTask(string name,int seconds,CancellationToken token) { Console.WriteLine("Task {0} 運行線上程={1}中,是否線上程池 :{2}",name,
Thread.CurrentThread.ManagedThreadId,Thread.CurrentThread.IsThreadPoolThread); for (int i = 0; i < seconds; i++) { Thread.Sleep(TimeSpan.FromSeconds(1)); if (token.IsCancellationRequested) { //取消操作,返回-1; return -1; } } return 42 * seconds; } } }
2.程式運行結果如下圖。
首先我們來看task1的創建代碼,我們給底層任務傳遞一次取消標誌,然後給任務的構造函數又傳遞了一次。
那為什麼要傳遞兩次取消標誌呢?
因為如果在task實際啟動之前取消它,則TPL的底層有責任處理這個取消操作。經過TPL底層處理過取消操作的task,如果再次啟動,則會拋出異常。如下圖。
然後需要我們自己寫代碼處理取消操作,在取消操作之後,任務的狀態仍然是RanToCompletion,從TPL來角度來講,這個task已經完成。
七、 處理task中的異常
通過此示例我們學習如何在task中拋出不同情況的異常,以及如何獲取這些異常信息。
1.程式代碼如下:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ThreadTPLDemo { class Program { static void Main(string[] args) { Console.WriteLine(" 處理Task中的異常信息。。。。。"); try { var task1 = Task.Run(() => RunTask("任務 1", 2)); int result = task1.Result; Console.WriteLine(" ——task1 狀態—{0}---值=={1}", task1.Status,result); } catch (Exception ex) { Console.WriteLine(" ——task1 錯誤信息—{0};innerException--{1}", ex.Message,
ex.InnerException==null?string.Empty:ex.InnerException.Message); } Console.WriteLine(" ——————————————————————"); try { var task2 = Task.Run(() => RunTask("任務 2", 2)); int result = task2.GetAwaiter().GetResult(); Console.WriteLine(" ——task2 狀態—{0}---值=={1}", task2.Status, result); } catch (Exception ex) { Console.WriteLine(" ——task2 錯誤信息—{0}", ex.Message); } Console.WriteLine(" ——————————————————————"); var task3 = new Task<int>(() => RunTask("任務 3", 2)); var task4 = new Task<int>(() => RunTask("任務 4", 2)); var completeTaskAll = Task.WhenAll(task3, task4); var exception = completeTaskAll.ContinueWith(t => Console.WriteLine(" ——task 錯誤信息—{0}", t.Exception)
, TaskContinuationOptions.OnlyOnFaulted); task3.Start(); task4.Start(); Thread.Sleep(7000); Console.Read(); } private static int RunTask(string name,int seconds) { Console.WriteLine("Task {0} 運行線上程={1}中,是否線上程池 :{2}",name,
Thread.CurrentThread.ManagedThreadId,Thread.CurrentThread.IsThreadPoolThread); Thread.Sleep(TimeSpan.FromSeconds(seconds)); throw new Exception("測試錯誤信息!"); return 42 * seconds; } } }
2.程式運行結果,如下圖。
當程式啟動時,創建一個任務task1並嘗試同步獲取結果。Result屬性的Get部分會使當前線程等待直到這個任務完成,並將異常傳播給當前線程。在這種情況下,通過catch代碼塊可以很容易地捕捉異常,不過這個異常是封裝異常。所以可以訪問InnerException來獲取異常信息。
Task2使用GetAwaiter與GetResult來獲取任務結果。這種情況下,不需要封裝異常,TPL會提取異常。如果底層只有一個task,一次只提取一個異常。
最後一個示例是兩個任務(task3,task4)拋出異常的情況 。通過後續操作來處理異常,只有之前的任務完成之前有異常,這個後續操作才會被觸發 。通過後續操作傳遞TaskContinuationOption.OnlyOrFaulted選項來實現 ,在拋出的異常中封裝了兩個異常。