在之前的文章中已经介绍过了Task的基本使用了,以及如何取消任务、任务继续等功能。本篇Post主要就Task的异常处理以及Wait功能进行些介绍。
在TPL中我们可以通过三种方式进行等待,一是通过CancellTaken的WaitHanle进行等待、第二种则是通过传统的Tread.Sleep方法、第三种则通过Thread.SpainWait方法。
CancellToken方式:
static void Main(string[] args) { var cancell = new CancellationTokenSource(); var token = cancell.Token; var task = new Task(() => { for (var i = 0; i < 100000; i++) { var cancelled = token.WaitHandle.WaitOne(10000); Console.WriteLine(" {0}. Cancelled? {1}", i, cancelled); if (cancelled) { throw new OperationCanceledException(token); } } }, token); task.Start(); Console.WriteLine("Press enter to cancel token."); Console.ReadLine(); cancell.Cancel(); Console.WriteLine("Main method complete. Press enter to finish."); Console.ReadLine(); }
每次我们等待十秒钟之后,在进行下次输出。
上面的功能如果我们要是通过Tread.Sleep方式实现:
var task = new Task(() => { for (var i = 0; i < 100000; i++) { Thread.Sleep(10000); var cancelled = token.IsCancellationRequested; Console.WriteLine(" {0}. Cancelled? {1}", i, cancelled); if (cancelled) { throw new OperationCanceledException(token); } } }, token);
Thread.SpainWait则跟上面的种方式完全不同,上面的两种方式都是会在线程调度程序不考虑改线程,直等到运行结束。而Thread.SpainWait的作用实质上会将处理器置于十分紧密的循环中,主要的作用是来实现同步锁的作用。并不常用,大部分情况下我们可以通过Lock的方式来实现。
在很多时候我们也许需要等待同时开启的几个线程完成之后再来做其他事,在TPL中提供了几种方式来等待任务执行。Task.Wait等待单个任务完成;Task.WaitAll等待所有的Task完成、TaskAny等在其中的任何一个或则多个任务完成。
Task.Wait:
Task.Wait共有5个重载:Wait()、Wait(CancellToken)、Wait(Int32)、Wait(TimeSpan)、Wait(TimeSpan、CancellToken)。各个重载方法的含义:
1)Wait():等待整个任务完成或则取消或则出现异常;
2)Wait(CancellToken):等待任务直到CancellToken调用取消或则完成,或则出现异常;
3)Wait(Int32):等待任务,未完成则到指定的时间;
4)Wait(TimeSpan):同上;
5)Wait(TimeSpan、CancellToken):等待任务到指定时间,或则CancellToken调用取消或则任务完成。
看个示例:
static void Main(string[] args) { var tokenSource = new CancellationTokenSource(); CancellationToken token = tokenSource.Token; Task task = createTask(token,6); task.Start(); Console.WriteLine("Wait() complete."); task.Wait(); Console.WriteLine("Task Completed."); task = createTask(token,3); task.Start(); Console.WriteLine("Wait(2) secs for task to complete."); bool completed = task.Wait(2000); Console.WriteLine("Wait ended - task completed: {0}", completed); task = createTask(token,4); task.Start(); Console.WriteLine("Wait(2,token) for task to complete."); completed = task.Wait(2000, token); Console.WriteLine("Wait ended - task completed: {0} task cancelled {1}", completed, task.IsCanceled); Console.WriteLine("Main method complete. Press enter to finish."); Console.ReadLine(); } static Task createTask(CancellationToken token,int loop) { return new Task(() => { for (int i = 0; i < loop; i++) { token.ThrowIfCancellationRequested(); Console.WriteLine("Task - Int value {0}", i); token.WaitHandle.WaitOne(1000); } }, token); }
循环都会等待1秒钟,这样我们可以看看Wait(2000)的效果,看看运行后的效果:
Task.WaitAll
WaitAll方法是等待所有的任务完成,也有5个重载,我们来看示例:
static void Main(string[] args) { var tokenSource = new CancellationTokenSource(); CancellationToken token = tokenSource.Token; var task1 = createTask(token,2); var task2 = createTask(token, 5); task1.Start(); task2.Start(); Console.WriteLine("Waiting for tasks to complete."); Task.WaitAll(task1, task2); Console.WriteLine("Tasks Completed."); Console.ReadKey(); }
其中WaitAll,也可以传递时间以及Token参数,进行等待时间以及取消Token的控制。
Task.WaitAny
waitany是等待任何一个任务完成,完成之后返回其完成的任务的Index:
static void Main(string[] args) { var tokenSource = new CancellationTokenSource(); CancellationToken token = tokenSource.Token; var task1 = createTask(token,2); var task2 = createTask(token, 5); task1.Start(); task2.Start(); Console.WriteLine("Waiting for tasks to complete."); var index = Task.WaitAny(task1, task2); Console.WriteLine("Tasks Completed.Index is {0}",index); Console.ReadKey(); }
任何应用程序都需要有异常处理机制,在TPL中提供了一套可靠的异常处理机制给我们使用。首先我啊们要知道在哪些环节会触发异常,在TPL中,异常的触发器主要是这几个:
Task.Wait(), Task.WaitAll(), Task,WaitAny(),Task.Result。而在TPL出现的异常都会以AggregateException的示例抛出,我们在进行基本的异常处理时,可以通过查看AggregateException的InnerExceptions来进行内部异常的捕获:
static void Main(string[] args) { var tokenSource = new CancellationTokenSource(); var token = tokenSource.Token; var task1 = new Task(() => { throw new NullReferenceException() { Source="task1"}; }); var task2 = new Task(() => { throw new ArgumentNullException("a", "a para can not be null") { Source="task2"}; }); var task3 = new Task(() => { throw new DivideByZeroException("Can not be zero") { Source = "task3" }; }); var task4 = new Task(() => { Console.WriteLine("Task4 ."); }); task1.Start(); task2.Start(); task3.Start(); task4.Start(); try { Task.WaitAll(task1, task2, task3, task4); } catch(AggregateException ex) { foreach (Exception inner in ex.InnerExceptions) { Console.WriteLine("Exception type {0} from {1}", inner.GetType(), inner.Source); } } Console.ReadLine(); }
上面的例子中通过便利AggregateException的InnerException进行详细异常的处理,效果:
同时,我们还可以通过Task的几个属性来判断Task的状态,如:IsCompleted, IsFaulted, IsCancelled,Exception。另外,AggregateException中还提供了
Handle方法来给我们方法来给我们处理每个内部 异常,每个异常发生时都会调用Handle传入的delegate ,同时我们需要通过返回True,False来告诉异常是否已经被处理,比如对于OperationCanceledException我们知道是取消了Task,是肯定可以处理的:
try { Task.WaitAll(task1, task2, task3, task4); } catch(AggregateException ex) { ex.Handle((e) => { if (e is OperationCanceledException) { return true; } else { return false; } }); }
本篇Post中,我们主要学习了Wait 、Wait for task以及异常的处理,希望对您有用。(PS:好长时间不写Blog了,之间间隔了好几个月。)