一. Task的各种返回值-Task<TResult>
PS: 在前面章节,我们介绍了Task类开启线程、线程等待、线程延续的方式,但我们并没有关注这些方式的返回值,其实他们都是有返回值的Task<TResult>,然后可以通过Task的实例调用Result属性来获取这个返回值。
下面我们分三类来介绍:
①:线程开启类的返回值, 使用Task<TResult>接受,或者直接使用Task接受,通过 实例.Result 来获取返回值。这里的线程开启类有多种,eg: Task.Run()、 task.start()、 Task.Factory.StartNew() 等。
②:线程延续类的返回值. eg:ContinueWith。
③:线程条件延续类的返回值. eg:WhenAll和WhenAny。
1. 线程开启类的返回值
1 { 2 Task<string> task1 = Task.Factory.StartNew(() => 3 { 4 Console.WriteLine("我是子线程哦"); 5 return "ok"; 6 }); 7 task1.Wait(); 8 Console.WriteLine("我是主线程,我要读取子线程task1的返回值为:{0}", task1.Result); 9 }
2. 线程延续类的返回值
1 {2 Task<int> task1 = Task.Run(() =>3 {4 Console.WriteLine("我是子线程1哦");5 return 2;6 });7 8 var task2 = task1.ContinueWith((t) =>9 { 10 Console.WriteLine("我是子线程2哦"); 11 12 //这里的t代表 task1 13 var num = t.Result + 2; 14 return num.ToString(); 15 }); 16 17 task2.Wait(); 18 Console.WriteLine("我是主线程,我要读取子线程task1的返回值为:{0}", task1.Result); 19 Console.WriteLine("我是主线程,我要读取子线程task2的返回值为:{0}", task2.Result); 20 }
3. 线程条件延续类
1 {2 Task<int> task1 = Task.Run(() =>3 {4 Console.WriteLine("我是子线程1哦");5 return 1;6 });7 Task<int> task2 = Task.Run(() =>8 {9 Console.WriteLine("我是子线程2哦"); 10 return 2; 11 }); 12 13 var task = Task.WhenAny(new Task<int>[2] { task1, task2 }); 14 task.Wait(); 15 16 //下面的值可能是1,也可能是2 17 Console.WriteLine("我是主线程,我要读取子线程task的返回值为:{0}", task.Result.Result); 18 }
二. 通用线程异常处理方案
1. 背景:我们想达到一个目的,当同时开启多个线程的时候,其中一个线程报错,不影响其他线程的执行,并且能把错误记下来。
2. 解决方案:多重try-catch,整个外侧主线程一个try-catch,然后线程执行业务再用一个try-catch包裹起来。
常规方式捕获异常:
1 {2 try3 {4 for (int i = 0; i < 5; i++)5 {6 string name = string.Format("name{0}", i);7 var task = Task.Run(() =>8 {9 try 10 { 11 //模拟某个线程出错 12 if (name == "name2") 13 { 14 throw new Exception(string.Format("线程执行失败,i={0}", name)); 15 } 16 else 17 { 18 Console.WriteLine(string.Format("线程执行执行成功,i={0}", name)); 19 } 20 } 21 catch (Exception ex) 22 { 23 Console.WriteLine(ex.Message); 24 } 25 26 }); 27 taskList.Add(task); 28 } 29 Task.WaitAll(taskList.ToArray()); 30 } 31 catch (Exception ex) 32 { 33 Console.WriteLine(ex.Message); 34 35 } 36 }
运行结果:我们发现所有的线程均执行完毕,且name2执行失败,并捕获。
补充一下:通过 AggregateException 类来捕获异常。
1 {2 try3 {4 for (int i = 0; i < 5; i++)5 {6 string name = string.Format("name{0}", i);7 var task = Task.Run(() =>8 {9 throw new Exception(string.Format("线程执行失败,i={0}", name)); 10 }); 11 taskList.Add(task); 12 } 13 Task.WaitAll(taskList.ToArray()); 14 } 15 catch (AggregateException aes) 16 { 17 foreach (var item in aes.InnerExceptions) 18 { 19 Console.WriteLine(item.Message); 20 } 21 } 22 }