上一篇我講解了await和async關鍵字,這兩個關鍵字的作用是將async限定的方法中await關鍵字后面的部分封裝成一個委托,該委托會在await修飾的Task完成后再執行。簡單的說,就是等待任務完成后,后面的程序才執行,且該等待不會造成線程阻塞。關鍵是在任務執行完成后,程序會繼續交給主線程執行。接下來,我來介紹在任務執行結束后,用新任務來執行方法。
廢話不多上,上代碼,我們來看看如何在任務結束后繼續由線程池繼續完成其他方法。
1 static void Main(string[] args) 2 { 3 RunAsync(); 4 Console.WriteLine("Async Run"); 5 Console.Read(); 6 } 7 public static void RunAsync() 8 { 9 var task = Task.Run(() => 10 { 11 Thread.Sleep(2000); 12 return "task finished"; 13 }); 14 //當task完成后,會在由線程池繼續執行 15 task.ContinueWith(t => 16 { 17 Thread.Sleep(2000); 18 Console.WriteLine(task.Result); 19 }); 20 }
可以看到,RunAsync方法中並沒有添加async和await關鍵字,但是運行程序后,主線程沒有被阻塞。這是因為ContinueWith會將委托參數“掛”到線程池的任務隊列中,該委托只有在task執行結束后,才會開始執行。ContinueWith方法會返回一個Task,該Task就是task執行結束后會開始執行的Task,該Task也是可以等待的。注意,ContinueWIth和await關鍵字的區別就在:await在任務完成后,會由主線程繼續執行,但是ContinueWith中的方法會繼續由線程池執行。
接下來,我們來了解一下,任務能否取消,以及取消后會發生什么。
C#提供的任務取消的方式是“協作式”取消,在構造任務時,需要先構造一個System.Threading.CancellationTaskSource對象,該對象看起來像這樣
1 public sealed class CancellationTaskSource:IDisposable{ 2 public CancellationTokenSource(); 3 public void Dispose();//釋放資源 4 public bool IsCancellationRequested{get;} 5 public CancellationToken Token{get;} 6 public void Cancel();//內部調用Cancel並傳遞false 7 public void Cancel(bool throwOnFirstException); 8 }
CancellationTaskSource對象中含有一個Token,它的類型是CancellationToken,這是一個輕量級值類型,它包含一個私有的對CancellationTaskSource的引用,當構造Task時,需要將這個Token傳入,如下所示
static void Main(string[] args){ CancelTask(); Console.WriteLine("Async Run"); Console.Read(); } public static void CancelTask() { //定時1000ms后自動取消任務 var cancelSource = new CancellationTokenSource(1000); Task.Run(() => { //模擬其他任務 Thread.Sleep(4000); //如果取消請求已發送,則不會顯示Task over if (!cancelSource.IsCancellationRequested) Console.WriteLine("Task over"); }, cancelSource.Token); cancelSource.Token.Register(() => Console.WriteLine("Task cancel")); }
運行后可以看到大約1秒后,控制台顯示了Task cancel,並且在沒有顯示Task over。證明任務被取消了。Task.Run()方法的其中一個重載是接受一個CancelToken參數,我們傳入的是cancelSource的Token,這表明cancelSource就可以控制Task的取消與否。我的做法是在1秒后自動取消任務,也可以手動調用cancelSource.Cancel()方法來顯式取消任務。我在Token上訂閱了取消的事件,在任務被取消后,執行我的方法,這里是控制台打印出Task cancel。也可以在任務中,利用“閉包”,將Token傳入到任務中,如下
1 static void Main(string[] args){ 2 CancelTask(); 3 Console.WriteLine("Async Run"); 4 Console.Read(); 5 } 6 public static void CancelTask(){ 7 var cancelSource = new CancellationTokenSource(1000); 8 Task.Run(() =>{ 9 int count = 0; 10 for (int i = 0; i < 1000; i++) 11 { 12 if (cancelSource.IsCancellationRequested) 13 break; 14 count+=i; 15 Thread.Sleep(30); 16 } 17 Console.WriteLine(count); 18 }, cancelSource.Token); 19 cancelSource.Token.Register(() => Console.WriteLine("Task cancel")); 20 }
手動取消有2個方法,一是cancelSource.Cancel(),另一個是cancelSource.CancelAfter(),示例如下
1 public static void CancelTask(){ 2 var cancelSource = new CancellationTokenSource(); 3 Task.Run(() => 4 { 5 Thread.Sleep(4000); 6 Console.WriteLine("Task over"); 7 }, cancelSource.Token); 8 cancelSource.Token.Register(() => Console.WriteLine("Task cancel")); 9 //此處調用cancelSource.Cancel()的話會立即結束任務 10 //該方法會大約1秒后取消任務 11 cancelSource.CancelAfter(1000); 12 }
該示例和上面的在構造函數中傳入1000ms的延時是一樣的效果,若調用Cancel(),則立刻結束任務。
以上就是本篇內容,介紹了Task.ContinueWith方法,以及如何取消任務。若文中不能滿足你的要求,可以查看諸如Task.Run()或者Task.ContinueWith()方法的幾個重載,還有其他的使用方法,本人並沒有將全部細枝末節全部學會,主要的目的是掌握多線程的基本使用方法。《CLR via C#》書中也有很詳細的講解,我的這個系列就是從該書中提取,有興趣的小伙伴可以看一看。歡迎有問題的和我在評論區交流。
下一篇,我給大家介紹一下C#並行的奇技淫巧:並行的For、Foreach循環以及PLINQ。