常規Thread中處理異常
使用Thread創建的子線程,需要在委托中捕捉,無法在上下文線程中捕捉
static void Main(string[] args) { ThreadStart threadStart = DoWork; Thread thread = new Thread(threadStart); thread.Start(); thread.Join(); } static void DoWork() { try { throw new Exception("子線程出現異常了"); } catch (Exception ex) { Trace.Assert(false, "Catch In Delegate"); } }
Task中處理異常
1.仍然可以在委托中捕獲異常
2.可以捕獲Task.Wait() 或者 Task.Result 的 AggregateException 異常
try { task.Wait(); } catch (AggregateException ex) { Console.WriteLine($"Error: {ex.GetType().Name}"); foreach (Exception item in ex.InnerExceptions) { Console.WriteLine($"{item.GetType().Name}, {item.Message}"); } }
AggregateException 是並行任務中捕獲的一組異常
通過延續任務捕獲前驅任務中的異常
static void Main(string[] args) { Task task = Task.Run(() => throw new Exception("前驅任務異常了")); Task faultedTask = task.ContinueWith(antecedentTask => { antecedentTask.Exception.Handle(eachE => { Console.WriteLine($"Error: {eachE.Message}"); return true; }); },TaskContinuationOptions.OnlyOnFaulted); faultedTask.Wait(); }
前驅任務:使用Run書寫的第一個任務就是前驅任務
延續任務:在一個任務后使用ContinueWith添加的任務就是延續任務,延續一般是一個全新的工作線程
TaskContinuationOptions:指定延續任務時的可配置項,默認情況下前驅任務完成后,立即執行延續任務,OnlyOnFaulted表示只有前驅任務失敗(出異常的時候)才會執行這一個延續任務
Task.Exception也是一個AggregateException 異常
注意:
1.當指定的TaskContinuationOptions與前驅任務運行結果不一致時,強制調用延續任務Wait()會引發TaskCanceledException異常
static void Main(string[] args) { Task task = new Task(() => { Console.WriteLine("前驅動任務執行中..."); }); Task faultedTask = task.ContinueWith(antecedentTask => { Console.WriteLine("延續動任務執行中..."); }, TaskContinuationOptions.OnlyOnFaulted); task.Start(); try { faultedTask.Wait(); } catch (AggregateException ex) { Console.WriteLine($"Error: {ex.GetType().Name}"); foreach (Exception item in ex.InnerExceptions) { Console.WriteLine($"{item.GetType().Name}, {item.Message}"); } } Console.WriteLine($"前驅任務狀態{task.Status}"); Console.WriteLine($"延續任務狀態{faultedTask.Status}"); }
Ctrl+F5 輸出
補充:
假如在前驅任務中出現了異常,如OnlyOnFaulted所願,會執行faultedTask任務,並且在faultedTask.Wait()中不會捕捉到前驅任務的異常,具體看下面一點
2.延續任務雖然在異步任務中提供了類似if else 的ContinueWith但是在異常處理上還是有點局限,看一個例子
static void Main(string[] args) { Task task = Task.Run(() => throw new Exception("前驅任務異常了")); Task task1 = task.ContinueWith(antecedentTask => { throw new Exception("延續任務1異常了"); }); Task task2 = task1.ContinueWith(antecedentTask => { throw new Exception("延續任務2異常了"); }); Task task3 = task2.ContinueWith(antecedentTask => { throw new Exception("延續任務3異常了"); }); try { task3.Wait(); } catch (AggregateException ex) { Console.WriteLine($"Error: {ex.GetType().Name}"); foreach (Exception item in ex.InnerExceptions) { Console.WriteLine($"{item.GetType().Name}, {item.Message}"); } } }
Ctrl+F5 輸出
其實這樣也可以理解,task3.Wait()只會收集task3所在工作線程上的異常,遺憾的是Task.Exception.InnerExceptions是一個只讀集合,這樣一來,每個任務的異常只能在各自委托中處理了,事實上也應該如此,可以使用TaskContinuationOptions進行靈活控制
使用CancellationTokenSource取消任務
static void Main(string[] args) { CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); cancellationTokenSource.Token.Register(() => { Console.WriteLine("任務取消了"); }); cancellationTokenSource.CancelAfter(2000); Task task = Task.Run(() => { while (true && !cancellationTokenSource.IsCancellationRequested) { Console.WriteLine("任務執行中..."); Thread.Sleep(300); } }, cancellationTokenSource.Token); task.Wait(); Console.WriteLine($"任務的最終狀態是:{task.Status}"); }
Ctrl+F5 輸出
正常取消的任務最終狀態是 RanToCompletion ,這里要注意的是,CancelAfter()是在這個方法調用的那一刻開始計時的(並非以Run開始計時,好吧,很好理解,我卻疑惑了半天)
小結:
結合 TaskContinuationOptions 和 CancellationTokenSource 可以很好處理多任務中異常,但是編寫在異步程序還是很繁瑣的,具體的在下一個筆記中會結合C#5.0做一個比較