.NET 并行(多核)编程系列之五 Task执行和异常处理
转载:http://www.cnblogs.com/yanyangtian/archive/2010/06/01/1749199.html
首先注意一点:这里提到的"等待"和之前文章提到的"休眠"意思是不一样的:
等待:在等待一个task的时候,这个task还是在运行之中的,"等待"相当于在监听运行的task的执行情况。
休眠:让tasku不运行。
在上篇文章中介绍了如果从Task中获取执行后的结果:在Task执行完成之后调用Task.Result获取。其实也可以用其他的方法等待Task执行完成而不获取结果,这是很有用的:如果你想等待一个task完成之后再去做其他的事情。而且我们还可以等待一个task执行完成,或者等待所有的task执行完成,或者等待很多task中的一个执行完成。因为Task是由内部的Scheduler管理的,调用wait方法,其实就是我们在监控task的执行,看看这个task是否执行完了,如果完成,那么wanit方法就返回true,反之。
1. 等待Task执行完成
1.1等待单独的一个Task执行完成
我们可以用Wait()方法来一直等待一个Task执行完成。当task执行完成,或者被cancel,或者抛出异常,这个方法才会返回。可以使用Wait()方法的不同重载。举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | static void Main( string [] args) { // create the cancellation token source CancellationTokenSource tokenSource = new CancellationTokenSource(); // create the cancellation token CancellationToken token = tokenSource.Token; // create and start the first task, which we will let run fully Task task = createTask(token); task.Start(); // wait for the task Console.WriteLine( "Waiting for task to complete." ); task.Wait(); Console.WriteLine( "Task Completed." ); // create and start another task task = createTask(token); task.Start(); Console.WriteLine( "Waiting 2 secs for task to complete." ); bool completed = task.Wait(2000); Console.WriteLine( "Wait ended - task completed: {0}" , completed); // create and start another task task = createTask(token); task.Start(); Console.WriteLine( "Waiting 2 secs for task to complete." ); completed = task.Wait(2000, token); Console.WriteLine( "Wait ended - task completed: {0} task cancelled {1}" , completed, task.IsCanceled); // wait for input before exiting Console.WriteLine( "Main method complete. Press enter to finish." ); Console.ReadLine(); } static Task createTask(CancellationToken token) { return new Task(() => { for ( int i = 0; i < 5; i++) { // check for task cancellation token.ThrowIfCancellationRequested(); // print out a message Console.WriteLine( "Task - Int value {0}" , i); // put the task to sleep for 1 second token.WaitHandle.WaitOne(1000); } }, token); } |
从上面的例子可以看出,wait方法子task执行完成之后会返回true。
注意:当在执行的task内部抛出了异常之后,这个异常在调用wait方法时会被再次抛出。后面再"异常处理篇"会讲述。
1.2.等待多个task
我们也可以用WaitAll()方法来一直到等待多个task执行完成。只有当所有的task执行完成,或者被cancel,或者抛出异常,这个方法才会返回。WiatAll()方法和Wait()方法一样有一些重载。
注意:如果在等在的多个task之中,有一个task抛出了异常,那么调用WaitAll()方法时就会抛出异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | static void Main( string [] args) { // create the cancellation token source CancellationTokenSource tokenSource = new CancellationTokenSource(); // create the cancellation token CancellationToken token = tokenSource.Token; // create the tasks Task task1 = new Task(() => { for ( int i = 0; i < 5; i++) { // check for task cancellation token.ThrowIfCancellationRequested(); // print out a message Console.WriteLine( "Task 1 - Int value {0}" , i); // put the task to sleep for 1 second token.WaitHandle.WaitOne(1000); } Console.WriteLine( "Task 1 complete" ); }, token); Task task2 = new Task(() => { Console.WriteLine( "Task 2 complete" ); }, token); // start the tasks task1.Start(); task2.Start(); // wait for the tasks Console.WriteLine( "Waiting for tasks to complete." ); Task.WaitAll(task1, task2); Console.WriteLine( "Tasks Completed." ); // wait for input before exiting Console.WriteLine( "Main method complete. Press enter to finish." ); Console.ReadLine(); } |
在上面的例子中,首先创建了两个task,注意我们创建的是可以被cancel的task,因为使用CancellationToken。而且在第一个task中还是用waitOne()休眠方法,其实目的很简单:使得这个task的运行时间长一点而已。之后我们就调用了WaitAll()方法,这个方法一直到两个task执行完成之后才会返回的。
1.3.等待多个task中的一个task执行完成
可以用WaitAny()方法来等待多个task中的一个task执行完成。通俗的讲就是:有很多的task在运行,调用了WaitAny()方法之后,只要那些运行的task其中有一个运行完成了,那么WaitAny()就返回了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | static void Main( string [] args) { // create the cancellation token source CancellationTokenSource tokenSource = new CancellationTokenSource(); // create the cancellation token CancellationToken token = tokenSource.Token; // create the tasks Task task1 = new Task(() => { for ( int i = 0; i < 5; i++) { // check for task cancellation token.ThrowIfCancellationRequested(); // print out a message Console.WriteLine( "Task 1 - Int value {0}" , i); // put the task to sleep for 1 second token.WaitHandle.WaitOne(1000); } Console.WriteLine( "Task 1 complete" ); }, token); Task task2 = new Task(() => { Console.WriteLine( "Task 2 complete" ); }, token); // start the tasks task1.Start(); task2.Start(); // wait for the tasks Console.WriteLine( "Waiting for tasks to complete." ); int taskIndex = Task.WaitAny(task1, task2); Console.WriteLine( "Task Completed. Index: {0}" , taskIndex); // wait for input before exiting Console.WriteLine( "Main method complete. Press enter to finish." ); Console.ReadLine(); } |
2. Task中的异常处理
在并行编程(TPL)中另外一个已经标准化了的操作就是"异常处理"。而且在并行编程中异常处理显得尤为重要,因为并行编程时与系统中的线程相关的,出了异常,你开发的程序就很有可能崩溃。
下面就详细介绍TPL中异常处理操作。
a.处理基本的异常。
在操作task的时候,只要出现了异常,.NET Framework就会把这些异常记录下来。例如在执行Task.Wait(),Task.WaitAll(),Task.WaitAny(),Task.Result.不管那里出现了异常,最后抛出的就是一个System.AggregateException.
System.AggregateException时用来包装一个或者多个异常的,这个类时很有用的,特别是在调用Task.WaitAll()方法时。因为在Task.WaitAll()是等待多个task执行完成,如果有任意task执行出了异常,那么这个异常就会被记录在System.AggregateException中,不同的task可能抛出的异常不同,但是这些异常都会被记录下来。
下面就是给出一个例子:在例子中,创建了两个task,它们都抛出异常。然后主线程开始运行task,并且调用WaitAll()方法,然后就捕获抛出的System.AggregateException,显示详细信息。

static void Main(string[] args) { // create the tasks Task task1 = new Task(() => { ArgumentOutOfRangeException exception = new ArgumentOutOfRangeException(); exception.Source = "task1"; throw exception; }); Task task2 = new Task(() => { throw new NullReferenceException(); }); Task task3 = new Task(() => { Console.WriteLine("Hello from Task 3"); }); // start the tasks task1.Start(); task2.Start(); task3.Start(); // wait for all of the tasks to complete // and wrap the method in a try...catch block try { Task.WaitAll(task1, task2, task3); } catch (AggregateException ex) { // enumerate the exceptions that have been aggregated foreach (Exception inner in ex.InnerExceptions) { Console.WriteLine("Exception type {0} from {1}", inner.GetType(), inner.Source); } } // wait for input before exiting Console.WriteLine("Main method complete. Press enter to finish."); Console.ReadLine(); }
从上面的例子可以看出,为了获得被包装起来的异常,需要调用System.AggregateException的InnerExceptions属性,这个属性返回一个异常的集合,然后就可以遍历这个集合。
而且从上面的例子可以看到:Exeception.Source属性被用来指明task1的异常时ArgumentOutRangeException.
b.使用迭代的异常处理Handler
一般情况下,我们需要区分哪些异常需要处理,而哪些异常需要继续往上传递。AggregateException类提供了一个Handle()方法,我们可以用这个方法来处理
AggregateException中的每一个异常。在这个Handle()方法中,返回true就表明,这个异常我们已经处理了,不用抛出,反之。
在下面的例子中,抛出了一个OperationCancelException,在之前的task的取消一文中,已经提到过:当在task中抛出这个异常的时候,实际上就是这个task发送了取消的请求。下面的代码中,描述了如果在AggregateException.Handle()中处理不同的异常。

static void Main(string[] args) { // create the cancellation token source and the token CancellationTokenSource tokenSource = new CancellationTokenSource(); CancellationToken token = tokenSource.Token; // create a task that waits on the cancellation token Task task1 = new Task(() => { // wait forever or until the token is cancelled token.WaitHandle.WaitOne(-1); // throw an exception to acknowledge the cancellation throw new OperationCanceledException(token); }, token); // create a task that throws an exception Task task2 = new Task(() => { throw new NullReferenceException(); }); // start the tasks task1.Start(); task2.Start(); // cancel the token tokenSource.Cancel(); // wait on the tasks and catch any exceptions try { Task.WaitAll(task1, task2); } catch (AggregateException ex) { // iterate through the inner exceptions using // the handle method ex.Handle((inner) => { if (inner is OperationCanceledException) { // ...handle task cancellation... return true; } else { // this is an exception we don't know how // to handle, so return false return false; } }); } // wait for input before exiting Console.WriteLine("Main method complete. Press enter to finish."); Console.ReadLine(); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?