Task 使用详细[基础操作,异步原则,异步函数,异步模式]

  • A+
所属分类:.NET技术
摘要

Task是FrameWork4.0开始引入的,FrameWork4.5又添加了一些功能,比如Task.Run(),async/await关键字等,

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 使用详细[基础操作,异步原则,异步函数,异步模式]

如果Task是长任务,可以添加TaskCreationOptions.LongRunning参数,使任务不运行在线程池上,有利于提升性能。

Task.Factory.StartNew(() => {     Console.WriteLine(         $"TaskFactoryStartNew IsBackGround:{CurrentThread.IsBackground}, IsThreadPool:{CurrentThread.IsThreadPoolThread}"); }, TaskCreationOptions.LongRunning);

Task 使用详细[基础操作,异步原则,异步函数,异步模式]

 1.2 Task 返回值/带参数

Task 有一个泛型子类Task<TResult>,允许返回一个值。

Task<string> task =Task.Run(()=>SayHello("Jack"));  string SayHello(string name) {     return "Hello " + name; }  Console.WriteLine(task.Result);

Task 使用详细[基础操作,异步原则,异步函数,异步模式]

通过任务的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(); }

Task 使用详细[基础操作,异步原则,异步函数,异步模式]

对于自治任务(没有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"; }

Task 使用详细[基础操作,异步原则,异步函数,异步模式]

如果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"; }

Task 使用详细[基础操作,异步原则,异步函数,异步模式]

当任务出现错误时,必须处理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 使用详细[基础操作,异步原则,异步函数,异步模式]

这个方法类似Task.Delay()方法。

 

待续。。。