线程(Thread)是建立并发的底层工具,所以有必定的局限性(不易获得返回值(必须经过建立共享域);异常的捕获和处理也麻烦;同时线程执行完毕后没法再次开启该线程),这些局限性会下降性能同时影响并发性的实现(不容易组合较小的并发操做实现较大的并发操做,会增长手工同步处理(加锁,发送信号)的依赖,容易出现问题)。html
线程池的(ThreadPool)的QueueUserWorkItem方法很容发起一次异步的计算限制操做。但这个技术一样有着许多限制,最大的问题是没有内建的机制让你知道操做在何时完成,也没有机制在操做完成时得到返回值。编程
而Task类能够解决上述全部的问题。并发
任务(Task)表示一个经过或不经过线程实现的并发操做,任务是可组合的,使用延续(continuation)可将它们串联在一块儿,它们可使用线程池减小启动延迟,可以使用回调方法避免多个线程同时等待I/O密集操做。异步
微软在.NET 4.0 引入任务(Task)的概念。经过System.Threading.Tasks命名空间使用任务。它是在ThreadPool的基础上进行封装的。Task默认都是使用池化线程,它们都是后台线程,这意味着主线程结束时其它任务也会随之中止。async
启动一个任务有多种方式,如如下示例:ide
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Console.WriteLine("主线程Id:{0}", Thread.CurrentThread.ManagedThreadId); 6 int workerThreadsCount, completionPortThreadsCount; 7 ThreadPool.GetAvailableThreads(out workerThreadsCount, out completionPortThreadsCount); 8 Console.WriteLine("剩余工做线程数:{0},剩余IO线程数{1}", workerThreadsCount, completionPortThreadsCount); 9 //第一种:实例化方式Start启动 10 { 11 Task task = new Task(() => 12 { 13 Test("one-ok"); 14 }); 15 task.Start(); 16 } 17 //第二种:经过Task类静态方法Run方式进行启动 18 { 19 Task.Run(() => 20 { 21 Test("two-ok"); 22 }); 23 } 24 //第三种:经过TaskFactory的StartNew方法启动 25 { 26 TaskFactory taskFactory = new TaskFactory(); 27 taskFactory.StartNew(() => 28 { 29 Test("three-ok"); 30 }); 31 } 32 //第四种:.经过Task.Factory进行启动 33 { 34 Task taskStarNew = Task.Factory.StartNew(() => 35 { 36 Test("four-ok"); 37 }); 38 } 39 //第五种:经过Task对象的RunSynchronously方法启动(同步,由主线程执行,会卡主线程) 40 { 41 Task taskRunSync = new Task(() => 42 { 43 Console.WriteLine("线程Id:{0},执行方法:five-ok", Thread.CurrentThread.ManagedThreadId); 44 }); 45 taskRunSync.RunSynchronously(); 46 } 47 Thread.Sleep(1000); 48 ThreadPool.GetAvailableThreads(out workerThreadsCount, out completionPortThreadsCount); 49 Console.WriteLine("剩余工做线程数:{0},剩余IO线程数{1}", workerThreadsCount, completionPortThreadsCount); 50 Console.ReadKey(); 51 } 52 static void Test(string o) 53 { 54 Thread.Sleep(2000); 55 Console.WriteLine("线程Id:{0},执行方法:{1}", Thread.CurrentThread.ManagedThreadId, o); 56 } 57 /* 58 * 做者:Jonins 59 * 出处:http://www.cnblogs.com/jonins/ 60 */ 61 }
执行结果:函数
上面示例中除去使用RunSynchronously方法启动的是同步任务(由启用的线程执行任务)外,其它几种方式内部都由线程池内的工做者线程处理。工具
说明:性能
1.事实上Task.Factory类型自己就是TaskFactory(任务工厂),而Task.Run(在.NET4.5引入,4.0版本调用的是后者)是Task.Factory.StartNew的简写法,是后者的重载版本,更灵活简单些。测试
2.调用静态Run方法会自动建立Task对象并当即调用Start
3.如Task.Run等方式启动任务并无调用Start,由于它建立的是“热”任务,相反“冷”任务的建立是经过Task构造函数。
Task有一个泛型子类Task<TResult>,它容许任务返回一个值。调用Task.Run,传入一个Func<Tresult>代理或兼容的Lambda表达式,而后查询Result属性得到结果。若是任务没有完成,那么访问Result属性会阻塞当前线程,直至任务完成。
1 public static Task<TResult> Run<TResult>(Func<TResult> function);
而任务的Status属性可用于跟踪任务的执行状态,以下所示:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Task<int> task = Task.Run(() => 6 { 7 int total = 0; 8 for (int i = 0; i <= 100; i++) 9 { 10 total += i; 11 } 12 Thread.Sleep(2000); 13 return total; 14 }); 15 Console.WriteLine("任务状态:{0}",task.Status); 16 Thread.Sleep(1000); 17 Console.WriteLine("任务状态:{0}", task.Status); 18 int totalCount = task.Result;//若是任务没有完成,则阻塞 19 Console.WriteLine("任务状态:{0}", task.Status); 20 Console.WriteLine("总数为:{0}",totalCount); 21 Console.ReadKey(); 22 } 23 }
执行以下:
Reulst属性内部会调用Wait(等待);
任务的Status属性是一个TaskStatus枚举类型:
1 public TaskStatus Status { get; }
说明以下:
枚举值 | 说明 |
Canceled | 任务已经过对其自身的 CancellationToken 引起 OperationCanceledException 对取消进行了确认,此时该标记处于已发送信号状态; 或者在该任务开始执行以前,已向该任务的 CancellationToken 发出了信号。 |
Created | 该任务已初始化,但还没有被计划。 |
Faulted | 因为未处理异常的缘由而完成的任务。 |
RanToCompletion | 已完成执行的任务。 |
Running | 任务正在运行,还没有完成。 |
WaitingForActivation | 该任务正在等待 .NET Framework 基础结构在内部将其激活并进行计划。 |
WaitingForChildrenToComplete | 该任务已完成执行,正在隐式等待附加的子任务完成。 |
WaitingToRun | 该任务已被计划执行,但还没有开始执行。 |
Task中有很是方便的对并行运行的任务集合获取返回值的方式,好比WhenAll和WhenAny。
WhenAll:等待提供的全部 Task 对象完成执行过程(全部任务所有完成)。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 List<Task<int>> taskList = new List<Task<int>>();//声明一个任务集合 6 TaskFactory taskFactory = new TaskFactory(); 7 for (int i = 0; i < 5; i++) 8 { 9 int total = i; 10 Task<int> task = taskFactory.StartNew(() => Test(total)); 11 taskList.Add(task);//将任务放进集合中 12 } 13 Console.WriteLine("主线程Id:{0},继续执行A.....", Thread.CurrentThread.ManagedThreadId); 14 Task<int[]> taskReulstList = Task.WhenAll(taskList);//建立一个任务,该任务将集合中的全部 Task 对象都完成时完成 15 for (int i = 0; i < taskReulstList.Result.Length; i++)//这里调用了Result,因此会阻塞线程,等待集合内全部任务所有完成 16 { 17 Console.WriteLine("返回值:{0}", taskReulstList.Result[i]);//遍历任务集合内Task返回的值 18 } 19 Console.WriteLine("主线程Id:{0},继续执行B.....", Thread.CurrentThread.ManagedThreadId); 20 Console.ReadKey(); 21 } 22 private static int Test(int o) 23 { 24 Console.WriteLine("线程Id:{0},Task执行成功,参数为:{1}", Thread.CurrentThread.ManagedThreadId, o); 25 Thread.Sleep(500 * o); 26 return o; 27 } 28 }
执行结果:
WhenAny:等待提供的任一 Task 对象完成执行过程(只要有一个任务完成)。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 List<Task<int>> taskList = new List<Task<int>>();//声明一个任务集合 6 TaskFactory taskFactory = new TaskFactory(); 7 for (int i = 0; i < 5; i++) 8 { 9 int total = i; 10 Task<int> task = taskFactory.StartNew(() => Test(total)); 11 taskList.Add(task);//将任务放进集合中 12 } 13 Console.WriteLine("主线程Id:{0},继续执行A.....", Thread.CurrentThread.ManagedThreadId); 14 Task<Task<int>> taskReulstList = Task.WhenAny(taskList);//建立一个任务,该任务将在集合中的任意 Task 对象完成时完成 15 Console.WriteLine("返回值:{0}", taskReulstList.Result.Result);//获得任务集合内最早完成的任务的返回值 16 Console.WriteLine("主线程Id:{0},继续执行B.....", Thread.CurrentThread.ManagedThreadId); 17 Console.ReadKey(); 18 } 19 private static int Test(int o) 20 { 21 Console.WriteLine("线程Id:{0},Task执行成功,参数为:{1}", Thread.CurrentThread.ManagedThreadId, o); 22 Thread.Sleep(500 * o); 23 return o; 24 } 25 }
执行结果(这里返回值确定会是0,由于休眠最短):
调用任务的Wait方法能够阻塞任务直至任务完成,相似于线程的join。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Task task = Task.Run(() => 6 { 7 Console.WriteLine("线程执行Begin"); 8 Thread.Sleep(2000); 9 Console.WriteLine("线程执行End"); 10 }); 11 Console.WriteLine("任务是否完成:{0}", task.IsCompleted); 12 task.Wait();//阻塞,直至任务完成 13 Console.WriteLine("任务是否完成:{0}", task.IsCompleted); 14 Console.ReadKey(); 15 } 16 }
执行以下:
注意:
线程调用Wait方法时,系统检测线程要等待的Task是否已经开始执行。若是是线程则会阻塞直到Task运行结束为止。但若是Task尚未开始执行任务,系统可能(取决于TaskScheduler)使用调用Wait的线程来执行Task,这种状况下调用Wait的线程不会阻塞,它会执行Task并当即返回。好处在于没有线程会被阻塞,因此减小了资源占用。很差的地方在于加入线程在调用Wait前已经得到了一个线程同步锁,而Task试图获取同一个锁,就会形成死锁的线程。
咱们知道为了建立一个Task,须要调用构造函数并传递一个Action或Action<object>委托,若是传递的是期待一个Object的方法,还必须向Task的构造函数穿都要传给操做的实参。还能够选择向构造器传递一些TaskCreationOptions标记来控制Task的执行方式。
TaskCreationOptions为枚举类型
枚举值 | 说明 |
None | 默认。 |
PreferFairness | 尽量公平的方式安排任务,即先进先执行。 |
LongRunning | 指定任务将是长时间运行的,会新建线程执行,不会使用池化线程。 |
AttachedToParent | 指定将任务附加到任务层次结构中的某个父级 |
DenyChildAttach | 任务试图和这个父任务链接将抛出一个InvalidOperationException |
HideScheduler | 强迫子任务使用默认调度而非父级任务调度 |
在默认状况下,Task内部是运行在池化线程上,这种线程会很是适合执行短计算密集做业。若是要执行长阻塞操做,则要避免使用池化线程。
在池化线程上运行一个长任务问题不大,可是若是要同时运行多个长任务(特别是会阻塞的任务),则会对性能产生影响。最好使用:TaskCreationOptions.LongRunning。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 int workerThreadsCount, completionPortThreadsCount; 6 ThreadPool.GetAvailableThreads(out workerThreadsCount, out completionPortThreadsCount); 7 Console.WriteLine("剩余工做线程数:{0},剩余IO线程数{1},主线程Id:{2}", workerThreadsCount, completionPortThreadsCount, Thread.CurrentThread.ManagedThreadId); 8 Task task = Task.Factory.StartNew(() => 9 { 10 Console.WriteLine("长任务执行,线程Id:{0}", Thread.CurrentThread.ManagedThreadId); 11 Thread.Sleep(2000); 12 }, TaskCreationOptions.LongRunning); 13 Thread.Sleep(1000); 14 ThreadPool.GetAvailableThreads(out workerThreadsCount, out completionPortThreadsCount); 15 Console.WriteLine("剩余工做线程数:{0},剩余IO线程数{1},主线程Id:{2}", workerThreadsCount, completionPortThreadsCount, Thread.CurrentThread.ManagedThreadId); 16 Console.ReadKey(); 17 } 18 }
执行结果以下:
注意:
若是使运行I/O密集任务,则可使用TaskCompletionSource和异步函数(asynchronous functions),经过回调(延续)实现并发性,而是不经过线程实现。
若是使运行计算密集性任务,则可使用一个生产者/消费者队列,控制这些任务的并发数量,避免出现线程和进程阻塞的问题。
延续(continuation)会告诉任务在完成后继续执行下面的操做。延续一般由一个回调方法实现,它会在操做完成以后执行一次。给一个任务附加延续的方法有两种
任务的方法GetAwaiter是Framework 4.5新增长的,而C# 5.0的异步功能使用了这种方法,所以它很是重要。给一个任务附加延续以下:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Task<int> task = Task.Run(() => 6 { 7 int total = 0; 8 for (int i = 0; i <= 100; i++) 9 { 10 total += i; 11 } 12 Thread.Sleep(2000); 13 return total; 14 }); 15 var awaiter = task.GetAwaiter(); 16 awaiter.OnCompleted(() => 17 { 18 int result = awaiter.GetResult();//在延续中获取Task的执行结果 19 Console.WriteLine(result); 20 }); 21 Console.ReadKey(); 22 } 23 }
执行结果控制台会打印:5050。
调用GetAwaiter会返回一个等待者(awaiter)对象,它会让先导(antecedent)任务在任务完成(或出错)以后执行一个代理。已经完成的任务也能够附加一个延续,这事延续会立刻执行。
注意:
1.等待者(awaiter)能够是任意对象,但必须包含特定的两个方法和一个Boolean类型属性。
1 public struct TaskAwaiter<TResult> : ICriticalNotifyCompletion, INotifyCompletion 2 { 3 public bool IsCompleted { get; } 4 public TResult GetResult(); 5 public void OnCompleted(Action continuation); 6 }
2.先导任务出现错误,那么当延续代码调用awaiter.GetResult()时就会从新抛出异常。咱们能够须要调用GetResult,而是直接访问先导任务的Result属性(task.Result)。
GetResult的好处是,当先导任务出现错误时,异常能够直接抛出而不封装在AggregateException中。
3.若是出现同步上下文,那么会自动捕捉它,而后延续提交到这个上下文中。在无需同步上下文的状况下一般不采用这种方法,使用ConfigureAwait代替它。它一般会使延续运行在先导任务所在的线程上,从而避免没必要要的过载。
1 var awaiter = task.ConfigureAwait(false).GetAwaiter();
另外一种附加延续的方法是调用任务的ContinueWith方法:
1 static void Main(string[] args) 2 { 3 Task<int> task = Task.Run(() => 4 { 5 int total = 0; 6 for (int i = 0; i <= 100; i++) 7 { 8 total += i; 9 } 10 Thread.Sleep(2000); 11 return total; 12 }); 13 task.ContinueWith(continuationAction => 14 { 15 int result = continuationAction.Result; 16 Console.WriteLine(result); 17 }); 18 Console.ReadKey(); 19 }
ContinueWith自己会返回一个Task,它很是适用于添加更多的延续。而后若是任务出现错误,咱们必须直接处理AggregateException。
若是想让延续运行在统一个线程上,必须指定 TaskContinuationOptions.ExecuteSynchronously;不然它会弹回线程池。ContinueWith特别适用于并行编程场景。
在使用ContinueWith时能够指定任务的延续选项即TaskContinuationOptions,它的前六个枚举类型与以前说的TaskCreationOptions枚举提供的标志彻底同样,补充后续几个枚举值:
枚举值 | 说明 |
LazyCancellation | 除非先导任务完成,不然禁止延续任务完成(取消)。 |
NotOnRanToCompletion | 指定不该在延续任务前面的任务已完成运行的状况下安排延续任务。 |
NotOnFaulted | 指定不该在延续任务前面的任务引起了未处理异常的状况下安排延续任务。 |
NotOnCanceled | 指定不该在延续任务前面的任务已取消的状况下安排延续任务。 |
OnlyOnCanceled | 指定只应在延续前面的任务已取消的状况下安排延续任务。 |
OnlyOnFaulted | 指定只有在延续任务前面的任务引起了未处理异常的状况下才应安排延续任务。 |
OnlyOnRanToCompletion | 指定只有在延续任务前面的任务引起了未处理异常的状况下才应安排延续任务。 |
ExecuteSynchronously | 指定但愿由先导任务的线程执行,先导任务完成后线程继续执行延续任务。 |
ExecuteSynchronously是指同步执行,两个任务都在同一个=线程一前一后的执行。
ContinueWith结合TaskContinuationOptions使用的示例:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Task<int> task = Task.Run(() => 6 { 7 int total = 0; 8 for (int i = 0; i <= 100; i++) 9 { 10 total += i; 11 } 12 if (total == 5050) 13 { 14 throw new Exception("错误");//这段代码能够注释或开启,用于测试 15 } 16 return total; 17 }); 18 //指定先导任务无报错的延续任务 19 task.ContinueWith(continuationAction => 20 { 21 int result = continuationAction.Result; 22 Console.WriteLine(result); 23 }, TaskContinuationOptions.NotOnFaulted); 24 //指定先导任务报错时的延续任务 25 task.ContinueWith(continuationAction => 26 { 27 foreach (Exception ex in continuationAction.Exception.InnerExceptions)//有关AggregateException异常处理后续讨论 28 { 29 Console.WriteLine(ex.Message); 30 } 31 }, TaskContinuationOptions.OnlyOnFaulted); 32 Console.ReadKey(); 33 } 34 }
执行结果会打印:报错,若是注释掉抛出异常的代码则会打印5050。
另外一种建立任务的方法是使用TaskCompletionSource。它容许建立一个任务,并能够任务分发给使用者,而且这些使用者可使用该任务的任何成员。它的实现原理是经过一个能够手动操做的“附属”任务,用于指示操做完成或出错的时间。
TaskCompletionSource的真正做用是建立一个不绑定线程的任务(手动控制任务工做流,可使你把建立任务和完成任务分开)。
这种方法很是适合I/O密集做业:能够利用全部任务的优势(它们可以生成返回值、异常和延续),但不会在操做执行期间阻塞线程。
例如,假设一个任务须要等待2秒,而后返回10,咱们的方法会返回在一个2秒后完成的任务,经过给任务附加一个延续就能够在不阻塞任何线程的前提下打印这个结果,以下:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var awaiter = Demo(2000).GetAwaiter();//获得任务经过延续输出返回值 6 awaiter.OnCompleted(() => 7 { 8 Console.WriteLine(awaiter.GetResult()); 9 }); 10 Console.WriteLine("主线程继续执行...."); 11 Console.ReadKey(); 12 } 13 static Task<int> Demo(int millis) 14 { 15 //建立一个任务完成源 16 TaskCompletionSource<int> taskCompletionSource = new TaskCompletionSource<int>(); 17 var timer = new System.Timers.Timer(millis) { AutoReset = false }; 18 timer.Elapsed += delegate 19 { 20 timer.Dispose(); taskCompletionSource.SetResult(10);//写入返回值 21 }; 22 timer.Start(); 23 return taskCompletionSource.Task;//返回任务 24 } 25 }
执行结果:
注意:若是屡次调用SetResult、SetException或SetCanceled,它们会抛出异常,而TryXXX会返回false。
一些状况下,后台任务可能运行很长时间,取消任务就很是有用了。.NET提供了一种标准的任务取消机制可用于基于任务的异步模式。
取消基于CancellationTokenSource类,该类可用于发送取消请求。请求发送给引用CancellationToken类的任务,其中CancellationToken类与CancellationTokenSource类相关联。
使用示例以下:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 //构造函数 指定延迟2秒后自动取消任务 6 CancellationTokenSource source = new CancellationTokenSource(2000); 7 //注册一个任务取消后执行的委托 8 source.Token.Register(() => 9 { 10 Console.WriteLine("线程Id:{0} 任务被取消后的业务逻辑正在运行", Thread.CurrentThread.ManagedThreadId); 11 }); 12 //启动任务,将取消标记源带入参数 13 Task.Run(() => 14 { 15 while (!source.IsCancellationRequested)//IsCancellationRequested为True时取消任务 16 { 17 Thread.Sleep(100); 18 Console.WriteLine("线程Id:{0} 任务正在运行", Thread.CurrentThread.ManagedThreadId); 19 } 20 }, source.Token); 21 //主线程挂起2秒后手动取消任务 22 { 23 //Thread.Sleep(2000); 24 //source.Cancel();//手动取消任务 25 } 26 //主线程不阻塞,2秒后自动取消任务 27 { 28 source.CancelAfter(2000); 29 } 30 Console.ReadKey(); 31 } 32 }
执行结果:
根据Register方法绑定任务取消后的委托
1 public CancellationTokenRegistration Register(Action callback); 2 public CancellationTokenRegistration Register(Action callback, bool useSynchronizationContext); 3 public CancellationTokenRegistration Register(Action<object> callback, object state); 4 public CancellationTokenRegistration Register(Action<object> callback, object state, bool useSynchronizationContext);
手动取消任务:Cancel方法
自动取消任务:
1.CancelAfter方法后面能够带入参数指定延迟多少后时间取消任务。
1 public void CancelAfter(TimeSpan delay); 2 public void CancelAfter(int millisecondsDelay);
2.CancellationTokenSource构造函数能够带入参数指定延迟多少时间后取消任务。
1 public CancellationTokenSource(TimeSpan delay); 2 public CancellationTokenSource(int millisecondsDelay);
任务绑定CancellationTokenSource对象,在Task源码中能够带入CancellationToken对象的启动任务方式均可以绑定CancellationTokenSource。
异步等待很是实用,所以它成为Task类的一个静态方法
经常使用的使用方式有2种,以下:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 //第1种 6 { 7 Task.Delay(2000).ContinueWith((o) => 8 { 9 Console.WriteLine("线程Id:{0},异步等待2秒后执行的逻辑", Thread.CurrentThread.ManagedThreadId); 10 }); 11 } 12 //第2种 13 { 14 Task.Delay(3000).GetAwaiter().OnCompleted(() => 15 { 16 Console.WriteLine("线程Id:{0},异步等待3秒后执行的逻辑", Thread.CurrentThread.ManagedThreadId); 17 }); 18 } 19 Console.WriteLine("主线程Id:{0},继续执行", Thread.CurrentThread.ManagedThreadId); 20 Console.ReadKey(); 21 } 22 }
执行结果以下:
Task.Delay是Thread.Sleep的异步版本。而它们的区别以下(引自 禅道 ):
1.Thread.Sleep 是同步延迟,Task.Delay异步延迟。
2.Thread.Sleep 会阻塞线程,Task.Delay不会。
3.Thread.Sleep不能取消,Task.Delay能够。
4. Task.Delay() 比 Thread.Sleep() 消耗更多的资源,可是Task.Delay()可用于为方法返回Task类型;或者根据CancellationToken取消标记动态取消等待。
5. Task.Delay() 实质建立一个运行给定时间的任务, Thread.Sleep() 使当前线程休眠给定时间。
与线程不一样,任务能够随时抛出异常。因此,若是任务中的代码抛出一个未处理异常,那么这个异常会自动传递到调用Wait()或Task<TResult>的Result属性的代码上。
任务的异常将会自动捕获并抛给调用者。为确保报告全部的异常,CLR会将异常封装在AggregateException容器中,该容器公开的InnerExceptions属性中包含全部捕获的异常,从而更适合并行编程。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 try 6 { 7 Task.Run(() => 8 { 9 throw new Exception("错误"); 10 }).Wait(); 11 } 12 catch (AggregateException axe) 13 { 14 foreach (var item in axe.InnerExceptions) 15 { 16 Console.WriteLine(item.Message); 17 } 18 } 19 Console.ReadKey(); 20 } 21 }
上述示例控制台会显示:错误
注意:
使用Task的IsFaulted和IsCanceled属性,就能够不从新抛出异常而检测出错的任务。
1.IsFaulted和IsCanceled都返回False,表示没有错误发生。
2.IsCanceled为True,则任务抛出了OperationCanceledOperation(取消线程正在执行的操做时在线程中抛出的异常)。
3.IsFaulted为True,则任务抛出另外一种异常,而Exception属性包含了该错误。
当子任务抛出异常时,经过调用Flatten方法,能够消除任意层次的嵌套以简化异常处理。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var parent = Task.Factory.StartNew(() => 6 { 7 int[] numbers = { 0 }; 8 var childFactory = new TaskFactory(TaskCreationOptions.AttachedToParent, TaskContinuationOptions.None); 9 childFactory.StartNew(() => 10 / numbers[0]);//除零 10 childFactory.StartNew(() => numbers[1]);//超出索引范围 11 childFactory.StartNew(() => throw null);//空引用 12 }); 13 try 14 { 15 parent.Wait(); 16 } 17 catch (AggregateException axe) 18 { 19 foreach (var item in axe.Flatten().InnerExceptions) 20 { 21 Console.WriteLine(item.Message); 22 } 23 } 24 Console.ReadKey(); 25 } 26 }
若是须要只捕获特定类型异常,并重抛其它类型的异常,Handle方法为此提供了一种快捷方式。
Handle接受一个predicate(异常断言),并在每一个内部异常上运行此断言。
1 public void Handle(Func<Exception, bool> predicate);
若是断言返回True,它认为该异常是“已处理”,当全部异常过滤以后:
1.若是全部异常是已处理的,异常不会抛出。
2.若是存在异常未处理,就会构造一个新的AggregateException对象来包含这些异常并抛出。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var parent = Task.Factory.StartNew(() => 6 { 7 int[] numbers = { 0 }; 8 var childFactory = new TaskFactory(TaskCreationOptions.AttachedToParent, TaskContinuationOptions.None); 9 childFactory.StartNew(() => 10 / numbers[0]);//除零 10 childFactory.StartNew(() => numbers[1]);//超出索引范围 11 childFactory.StartNew(() => throw null);//空引用 12 }); 13 try 14 { 15 try 16 { 17 parent.Wait(); 18 } 19 catch (AggregateException axe) 20 { 21 axe.Flatten().Handle(ex => 22 { 23 if (ex is DivideByZeroException) 24 { 25 Console.WriteLine("除零-错误处理完毕"); 26 return true; 27 } 28 if (ex is IndexOutOfRangeException) 29 { 30 Console.WriteLine("超出索引范围-错误处理完毕"); 31 return true; 32 } 33 return false;//全部其它 异常从新抛出 34 }); 35 36 } 37 } 38 catch (AggregateException axe) 39 { 40 foreach (var item in axe.InnerExceptions)//捕获从新抛出的异常 41 { 42 Console.WriteLine(item.Message); 43 } 44 } 45 Console.ReadKey(); 46 } 47 }
执行结果:
1.async和await这两个关键字下篇记录。
2.任务调度器(TaskScheduler)是Task之因此如此灵活的本质,咱们常说Task是在ThreadPool上更升级化的封装,其实很大程度上归功于这个对象,考虑下篇要不要说一下,但其实我看的都头疼...
3.Task类包含不少的重载,最好F12跳到Task内熟悉下结构。
CLR via C#(第4版) Jeffrey Richter
C#高级编程(第10版) C# 6 & .NET Core 1.0 Christian Nagel
果壳中的C# C#5.0权威指南 Joseph Albahari
C#并发编程 经典实例 Stephen Cleary
...