- A+
Task是FrameWork4.0开始引入的,FrameWork4.5又添加了一些功能,比如Task.Run(),async/await关键字等,
在.NET FrameWork4.5之后,基于任务的异步处理已经成为主流模式, (Task-based Asynchronous Pattern,TAP)基于任务的异步模式。
在使用异步函数之前,先看下Task的基本操作。
一. Task 基本操作
1.1 Task 启动方式
Task.Run(()=>Console.WriteLine("Hello Task"));
Task.Factory.StartNew(()=>Console.WriteLine("Hello Task"));
Task.Run是Task.Factory.StartNew的快捷方式。
启动的都是后台线程,并且默认都是线程池的线程
Task.Run(() => { Console.WriteLine( $"TaskRun IsBackGround:{CurrentThread.IsBackground}, IsThreadPool:{CurrentThread.IsThreadPoolThread}"); }); Task.Factory.StartNew(() => { Console.WriteLine( $"TaskFactoryStartNew IsBackGround:{CurrentThread.IsBackground}, IsThreadPool:{CurrentThread.IsThreadPoolThread}"); });
如果Task是长任务,可以添加TaskCreationOptions.LongRunning参数,使任务不运行在线程池上,有利于提升性能。
Task.Factory.StartNew(() => { Console.WriteLine( $"TaskFactoryStartNew IsBackGround:{CurrentThread.IsBackground}, IsThreadPool:{CurrentThread.IsThreadPoolThread}"); }, TaskCreationOptions.LongRunning);
1.2 Task 返回值/带参数
Task 有一个泛型子类Task<TResult>,允许返回一个值。
Task<string> task =Task.Run(()=>SayHello("Jack")); string SayHello(string name) { return "Hello " + name; } Console.WriteLine(task.Result);
通过任务的Result属性获取返回值,这是会堵塞线程,尤其是在桌面客户端程序中,谨慎使用Task.Result,容易导致死锁!
同时带参数的方式也不是很合理,后面可以被async/await方式直接替代。
1.3 Task 异常/异常处理
当任务中的代码抛出一个未处理异常时,调用任务的Wait()或者Result属性时,异常会被重新抛出。
var task = Task.Run(ThrowError); try { task.Wait(); } catch(AggregateException ex) { Console.WriteLine(ex.InnerException is NullReferenceException ? "Null Error!" : "Other Error"); } void ThrowError() { throw new NullReferenceException(); }
对于自治任务(没有wait()和Result或者是延续的任务),使用静态事件TaskScheduler.UnobservedTaskException可以在全局范围订阅未观测的异常。
以便记录错误日志
1.4 Task 延续
延续通常由一个回调方法实现,该方法会在任务完成之后执行,延续方法有两种
(1)调用任务的GetAwaiter方法,将返回一个awaiter对象。这个对象的OnCompleted方法告知任务当执行完毕或者出错时调用一个委托。
Task<string> learnTask = Task.Run(Learn); var awaiter = learnTask.GetAwaiter(); awaiter.OnCompleted(() => { var result = awaiter.GetResult(); Console.WriteLine(result); }); string Learn() { Console.WriteLine("Learn Method Executing"); Thread.Sleep(1000); return "Learn End"; }
如果learnTask任务出现错误,延续代码awaiter.GetResult()将重新抛出异常,其中GetResult可以直接得到原始的异常,如果用Result属性方法,只能解析AggergateException.
这种延续方法更适用于富客户端程序,延续可以提交到同步上下文,延续回到UI线程中。
当编写库文件,可以使用ConfigureAwait方法,延续代码一会运行在任务运行的线程上,从而避免不必要的切换开销。
var awaiter =learnTask.ConfigureAwait(false).GetAwaiter();
(2)另一种方法使用ContiuneWith
Task<string> learnTask = Task.Run(Learn); learnTask.ContinueWith(antecedent => { var result = learnTask.Result; Console.WriteLine(result); }); string Learn() { Console.WriteLine("Learn Method Executing"); Thread.Sleep(1000); return "Learn End"; }
当任务出现错误时,必须处理AggregateException, ContiuneWith更适合并行编程场景。
1.5 TaskCompletionSource类使用
从如下源码中可以看出当实例化TaskCompletionSource时,构造函数会新建一个Task任务。
public class TaskCompletionSource { private readonly Task _task; /// <summary>Creates a <see cref="TaskCompletionSource"/>.</summary> public TaskCompletionSource() => _task = new Task(); /// <summary> /// Gets the <see cref="Tasks.Task"/> created /// by this <see cref="TaskCompletionSource"/>. /// </summary> /// <remarks> /// This property enables a consumer access to the <see cref="Task"/> that is controlled by this instance. /// The <see cref="SetResult"/>, <see cref="SetException(Exception)"/>, <see cref="SetException(IEnumerable{Exception})"/>, /// and <see cref="SetCanceled"/> methods (and their "Try" variants) on this instance all result in the relevant state /// transitions on this underlying Task. /// </remarks> public Task Task => _task; }
它的真正的作用是创建一个不绑定线程的任务。
eg: 可以使用Timer类,CLR在定时之后触发一个事件,而无需使用线程。
实现通用Delay方法:
Delay(5000).GetAwaiter().OnCompleted(()=>{ Console.WriteLine("Delay End"); }); Task Delay(int millisecond) { var tcs = new TaskCompletionSource<object>(); var timer = new System.Timers.Timer(millisecond) { AutoReset = false }; timer.Elapsed += delegate { timer.Dispose(); tcs.SetResult(null); }; timer.Start(); return tcs.Task; }
这个方法类似Task.Delay()方法。
待续。。。