C# Task与async/await异步任务异常处理
Task异步任务里面如果发生了未经处理的异常并不会终止程序的正常运行,如果是Thread创建的线程里发生了异常会终止程序的运行(控制台项目程序测试得出的结论),
由于async异步方法的返回值只能为void、Task和Task<T>所以也可以认为async异步方法产生的异常也不会导致程序的终止
对于异步任务的异常处理关键是要把异步任务里的异常传播给处理异常的线程,我认为可以有以下三种方式:
(1)使用延续任务
可以在延续任务里通过Task的 IsFaulted 属性值来判断任务是否由于未处理的异常而完成,然后通过 Flatten 方法展开异常
示例:
首先定义异步任务里运行的方法
public static async Task<string> TaskMethod1230(string taskName,double seconds) { await Task.Delay(TimeSpan.FromSeconds(seconds)); Console.WriteLine($"{taskName} Run"); throw new Exception(taskName+" have Exception"); return taskName; }
使用延续任务处理异常代码如下:
public static void ContinueHandleException() { var whenAllTasks = new List<Task>(); // Task<T>继承Task Task t1 = TaskMethod1230("task1", 1.1); whenAllTasks.Add(t1); Task t2 = TaskMethod1230("task2", 1.2); whenAllTasks.Add(t2); // 使用延续任务来处理异常 Task.WhenAll(whenAllTasks.ToArray()).ContinueWith(t => { // 前面的任务是由于未处理的异常而完成 if (t.IsFaulted) { Console.WriteLine("------使用延续任务来处理异常-------"); var exceptions = t.Exception.Flatten().InnerExceptions; foreach (var e in exceptions) { Console.WriteLine(e.Message); } } // 前面的任务正常完成 // some code Console.WriteLine("延续任务已完成"); Console.WriteLine("----------------------------------"); }); }
测试代码:
static void Main(string[] args) { ContinueHandleException(); //ResultHandleException(); // 使用 async/await的异步任务会自动运行 //Task t4 = AwaitHandleException("task4", 4.4); Console.Read(); }
运行结果:
(2)使用Task<T>.Result获取异步任务结果时,可以把异步任务的异常传播给获取Result的线程
获取Task<T>.Result引发异常时,打印出来的Exception.Message似乎都是“发生一个或多个错误。”
示例代码:
public static void ResultHandleException() { Task<string> t3 = TaskMethod1230("task3", 3.3); // 使用 task.Result会把任务的异常传播给调用线程(即task.Result代码行所在的线程) // 只有Task<T>才有Result属性 try { var t3_res = t3.Result; // 这行代码可以接收到任务传播过来的异常 } catch (Exception ex) { Console.WriteLine("------使用 task.Result会把任务的异常传播给调用线程-------"); Console.WriteLine(ex.Message); Console.WriteLine("----------------------------------"); } }
测试代码:
static void Main(string[] args) { //ContinueHandleException(); ResultHandleException(); // 使用 async/await的异步任务会自动运行 //Task t4 = AwaitHandleException("task4", 4.4); Console.Read(); }
运行结果:
(3)使用await获取异步任务的结果时也可以把异步任务的异常传播给await获取结果的所在线程
但是使用这种方法有一点需要注意:即如果异常是一个组合异常,如果我们还是使用
try{ await tasks;}catct(Exception ex){}这种模式,则只能获取组合任务的第一个异常,如果是组合异常,可以通过获取组合异常的实例来逐个处理异常
组合异常只获取第一个异常实例如下:
public static async Task<string> AwaitHandleException(string taskName, double seconds) { var t1 = TaskMethod1230(taskName + "_1", seconds); var t2 = TaskMethod1230(taskName + "_2", seconds); var allTasks = Task.WhenAll(t1, t2); try { // 在async/await中,使用await会把异步任务的异常传播给调用线程(即await代码行所在的线程) // 由于单个Task<string> 返回string,所以这里返回string数组 string[] res=await allTasks; // 这行代码可以接收到任务传播过来的异常 } catch (Exception ex) { Console.WriteLine("------使用await会把异步任务的异常传播给调用线程-------"); Console.WriteLine(ex.Message); Console.WriteLine("----------------------------------"); } return "AsyncException Ok"; }
测试方法:
static void Main(string[] args) { //ContinueHandleException(); //ResultHandleException(); // 使用 async/await的异步任务会自动运行 Task t4 = AwaitHandleException("task4", 4.4); Console.Read(); }
运行结果:
这里只获取到了第一个异常
通过获取组合异常的实例来逐个处理异常实例如下:
public static async Task<string> AwaitHandleExceptions(string taskName, double seconds) { var t1 = TaskMethod1230(taskName + "_1", seconds); var t2 = TaskMethod1230(taskName + "_2", seconds); //var allTasks = Task.WhenAll(t1, t2); Task<string[]> allTaskSEx = Task.WhenAll(t1, t2);//再创建一个Task try { // 在async/await中,使用await会把异步任务的异常传播给调用线程(即await代码行所在的线程) // 由于单个Task<string> 返回string,所以这里返回string数组 //string[] res=await allTasks; // 这行代码可以接收到任务传播过来的异常 await allTaskSEx; // 使用await传播异常 } catch { Console.WriteLine("------使用await会把异步任务的异常传播给调用线程-------"); var exceptions = allTaskSEx.Exception; if(exceptions != null) { foreach(var ex in exceptions.InnerExceptions) { Console.WriteLine(ex.Message); } } Console.WriteLine("----------------------------------"); } return "AsyncException Ok"; }
测试代码:
static void Main(string[] args) { //ContinueHandleException(); //ResultHandleException(); // 使用 async/await的异步任务会自动运行 Task t4 = AwaitHandleExceptions("task4", 4.4); Console.Read(); }
运行结果:
可以看到通过两个异常都成功处理了