前言:前面有篇从应用层面上面介绍了下多线程的几种用法,有博友就说到了async, await等新语法。确实,没有异步的多线程是单调的、乏味的,async和await是出如今C#5.0以后,它的出现给了异步并行变成带来了很大的方便。异步编程涉及到的东西仍是比较多,本篇仍是先介绍下async和await的原理及简单实现。html
C#基础系列目录:面试
以前的那篇 C#基础系列——多线程的常见用法详解 就讲到了多线程new Thread()的方式对于有返回值类型的委托是没有解决方案的,若是须要返回值,必需要依靠异步的方式。了解异步以前,咱们先来看看Thread对象的升级版本Task对象:编程
一、Task对象的前世此生:Task对象是.Net Framework 4.0以后出现的异步编程的一个重要对象。在必定程度上来讲,Task对象能够理解Thread对象的一个升级产品。既然是升级产品,那它确定有他的优点,好比咱们上面Thread对象不能解决的问题:对于有返回值类型的委托。Task对象就能简单的解决。设计模式
static void Main(string[] args) { Console.WriteLine("执行GetReturnResult方法前的时间:" + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); var strRes = Task.Run<string>(() => { return GetReturnResult(); });//启动Task执行方法 Console.WriteLine("执行GetReturnResult方法后的时间:" + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); Console.WriteLine(strRes.Result);//获得方法的返回值 Console.WriteLine("获得结果后的时间:" + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); Console.ReadLine(); } static string GetReturnResult() { Thread.Sleep(2000); return "我是返回值"; }
先来看结果:多线程
从结果分析可知在执行var strRes = Task.Run<string>(() => { return GetReturnResult(); })这一句后,主线程并无阻塞去执行GetReturnResult()方法,而是开启了另外一个线程去执行GetReturnResult()方法。直到执行strRes.Result这一句的时候主线程才会等待GetReturnResult()方法执行完毕。为何说是开启了另外一个线程,咱们经过线程ID能够看得更清楚:异步
static void Main(string[] args) { Console.WriteLine("执行GetReturnResult方法前的时间:" + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); var strRes = Task.Run<string>(() => { return GetReturnResult(); }); Console.WriteLine("执行GetReturnResult方法后的时间:" + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); Console.WriteLine("我是主线程,线程ID:" + Thread.CurrentThread.ManagedThreadId); Console.WriteLine(strRes.Result); Console.WriteLine("获得结果后的时间:" + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); Console.ReadLine(); } static string GetReturnResult() { Console.WriteLine("我是GetReturnResult里面的线程,线程ID:" + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(2000); return "我是返回值"; }
结果:async
由此能够得知,Task.Run<string>(()=>{}).Reslut是阻塞主线程的,由于主线程要获得返回值,必需要等方法执行完成。异步编程
Task对象的用法以下:函数
//用法一 Task task1 = new Task(new Action(MyAction)); //用法二 Task task2 = new Task(delegate { MyAction(); }); //用法三 Task task3 = new Task(() => MyAction()); Task task4 = new Task(() => { MyAction(); }); task1.Start(); task2.Start(); task3.Start(); task4.Start();
由上可知,Task对象的构造函数传入的是一个委托,既然能传入Action类型的委托,可想而知Action的16中类型的参数又能够派上用场了。因而乎Task对象参数的传递就不用多说了吧。详见 C#基础系列——委托和设计模式(一)里面Action委托的用法。post
二、初识 async & await。
static void Main(string[] args) { Console.WriteLine("我是主线程,线程ID:{0}", Thread.CurrentThread.ManagedThreadId); TestAsync(); Console.ReadLine(); } static async Task TestAsync() { Console.WriteLine("调用GetReturnResult()以前,线程ID:{0}。当前时间:{1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); var name = GetReturnResult(); Console.WriteLine("调用GetReturnResult()以后,线程ID:{0}。当前时间:{1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); Console.WriteLine("获得GetReturnResult()方法的结果:{0}。当前时间:{1}", await name, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); } static async Task<string> GetReturnResult() { Console.WriteLine("执行Task.Run以前, 线程ID:{0}", Thread.CurrentThread.ManagedThreadId); return await Task.Run(() => { Thread.Sleep(3000); Console.WriteLine("GetReturnResult()方法里面线程ID: {0}", Thread.CurrentThread.ManagedThreadId); return "我是返回值"; }); }
结果:
咱们来看看程序的执行过程:
由上面的结果能够获得以下结论:
(1)在async标识的方法体里面,若是没有await关键字的出现,那么这种方法和调用普通的方法没什么区别。
(2)在async标识的方法体里面,在await关键字出现以前,仍是主线程顺序调用的,直到await关键字的出现才会出现线程阻塞。
(3)await关键字能够理解为等待方法执行完毕,除了能够标记有async关键字的方法外,还能标记Task对象,表示等待该线程执行完毕。因此await关键字并非针对于async的方法,而是针对async方法所返回给咱们的Task。
(4)是否async关键字只能标识返回Task对象的方法呢。咱们来试试:
异步方法的返回类型必须为void、Task或者Task<T>类型。也就是说async要么是void,要么和Task关联。
三、除了await关键字,Task对象还有另一种方式等待执行结果。
static async Task TestAsync() { Console.WriteLine("调用GetReturnResult()以前,线程ID:{0}。当前时间:{1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); var name = GetReturnResult(); Console.WriteLine("调用GetReturnResult()以后,线程ID:{0}。当前时间:{1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); Console.WriteLine("获得GetReturnResult()方法的结果:{0}。当前时间:{1}", name.GetAwaiter().GetResult(), DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); }
这样能够获得相同的结果。
name.GetAwaiter()这个方法获得的是一个TaskAwaiter对象,这个对象表示等待完成的异步任务的对象,并提供结果的参数。因此除了能完成await关键字的等待以外,它还能作一些其余的操做。咱们将TaskAwaiter转到定义
public struct TaskAwaiter<TResult> : ICriticalNotifyCompletion, INotifyCompletion { public bool IsCompleted { get; } public TResult GetResult(); public void OnCompleted(Action continuation); public void UnsafeOnCompleted(Action continuation); }
IsCompleted:获取一个值,该值指示异步任务是否已完成。
GetResult():获得执行的结果。这个方法和await关键字效果相同。
OnCompleted():传入一个委托,在任务执行完成以后执行。
UnsafeOnCompleted():计划与此 awaiter 相关异步任务的延续操做。
由此能够看出,await关键字实际上就是调用了TaskAwaiter对象的GetResult()方法。