ASP.NET 中的 async,await
新学.net,最近用到了ABP框架,发现了如下代码:
public override async Task<UserDto> Get(EntityDto<long> input) { var user = await base.Get(input); var userRoles = await _userManager.GetRolesAsync(user.Id); user.Roles = userRoles.Select(ur => ur).ToArray(); return user; }
看到后,对async , Task , await 完全不理解,就查阅了相关资料。
简单说一下我的理解,这3个关键字都是多线程机制的一部分,目的是让处理用户请求的线程尽快结束此次请求,结束后,就可以用这个线程继续接收其他的请求。
而费时的异步操作,交给后台线程处理(这里的后台线程,应该不能响应用户请求)。这样一来,就能让服务器更快地响应请求。
Task对象中封装着线程相关的对象,async 和 await 都是为Task服务。 想在函数中使用await,就必须声明函数为async的。这是因为这里的await,理解为挂起更为恰当,当遇到await后,所在的函数执行必须挂起并立即返回主函数,而等待异步处理的结果得出后,继续执行接下来的代码(需要注意的是,await 的异步函数中的代码可能会被同步执行一部分,这要看后面的异步函数的内容。即await 后面的异步函数中,到底到哪里使用了系统级别的线程(任务)机制,线程(任务)启动前的代码,会被同步执行,启动后的代码,会等到线程(任务)返回结果后,再继续执行)。这样的函数是特殊的,需要一个async来标识。
而这里的Task<UserDto> 可以理解为,一个能够返回UserDto结果的,线程任务。Abp外层框架应该会对其进行await形式的调用,把结果返回给前台。
关于await 和 async 的理解,我贴2段代码,这2段代码是在搜资料时,从网上找到的,我对其进行了改进,感谢原作者的付出!
第一段
using System; using System.Threading; using System.Threading.Tasks; namespace ConsoleApp2 { class Program { static void Main(string[] args) { Console.WriteLine("Main start-----Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId); Test(); Console.WriteLine("-----Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId); Console.WriteLine("-----Main end:{0}", Thread.CurrentThread.ManagedThreadId); Console.ReadLine(); } static async Task Test() { Console.WriteLine("======Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId); // 方法打上async关键字,就可以用await调用同样打上async的方法 await GetName(); Console.WriteLine("Middle---------Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId); await GetName2(); Console.WriteLine("##########Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId); } static async Task GetName() { Console.WriteLine("*****Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId); // Delay 方法来自于.net 4.5 // await Task.Delay(3000); // 返回值前面加 async 之后,方法里面就可以用await了 await Task.Run(() => { Thread.Sleep(3000); Console.WriteLine("^^^^^^^^Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId); }); Console.WriteLine("$$$$$$$Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId); } static async Task GetName2() { Console.WriteLine("!!!!!Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId); await Task.Run(() => { Thread.Sleep(3000); Console.WriteLine("++++++++Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId); }); Console.WriteLine("~~~~~~Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId); } } }
输出如下:
Main start-----Current Thread Id :1 ======Current Thread Id :1 *****Current Thread Id :1 -----Current Thread Id :1 -----Main end:1 ^^^^^^^^Current Thread Id :3 $$$$$$$Current Thread Id :3 Middle---------Current Thread Id :3 !!!!!Current Thread Id :3 ++++++++Current Thread Id :4 ~~~~~~Current Thread Id :4 ##########Current Thread Id :4
上面这段代码,可以明显地看 代码运行的线程,await会对被它阻塞的代码的运行线程产生影响!
第二段代码:
using System; using System.Threading; using System.Threading.Tasks; namespace ConsoleApp1 { class Program { static void Main(string[] args) { Console.WriteLine("我是主线程,线程ID:{0}", Thread.CurrentThread.ManagedThreadId); TestAsync(); Console.WriteLine("-----------------TestAsync 返回--------------------------"); Console.ReadLine(); } //为了在函数体内使用await,必须写async,表示这是个异步函数。 //而这里使用 await 的作用,就是能让 TestAsync 函数立即返回main函数,去执行main之后的逻辑,而不用等待 await 的task.在await的task执行完毕后,继续执行当前task。 //对于一个async函数,可以使用await 去等待结果返回,也可以不使用,就像main函数这样,实现真正的异步效果,直接运行到了函数结尾。 static async Task TestAsync() { Console.WriteLine("调用GetReturnResult()之前,线程ID:{0}。当前时间:{1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); //创建task,但没有等待 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.Result, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); //不会阻塞上层调用的写法 //这个await 找到最终的那个 新线程await,并 跳过等待结果,直接返回 Console.WriteLine("得到GetReturnResult()方法的结果:{0}。当前时间:{1}", await name, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); Console.WriteLine("await 后面的逻辑 => TestAsync 最后一句"); } static Task<string> GetReturnResult() { Console.WriteLine("执行Task.Run之前, 线程ID:{0}", Thread.CurrentThread.ManagedThreadId); return Task.Run(() => { Console.WriteLine("并行线程:GetReturnResult()方法里面线程ID: {0}", Thread.CurrentThread.ManagedThreadId); Console.WriteLine("并行线程:running...."); Thread.Sleep(3000); return "我是返回值"; }); } } }
结果如下:
我是主线程,线程ID:1 调用GetReturnResult()之前,线程ID:1。当前时间:2018-06-28 03:06:32 执行Task.Run之前, 线程ID:1 调用GetReturnResult()之后,线程ID:1。当前时间:2018-06-28 03:06:32 -----------------TestAsync 返回-------------------------- 并行线程:GetReturnResult()方法里面线程ID: 3 并行线程:running.... 得到GetReturnResult()方法的结果:我是返回值。当前时间:2018-06-28 03:06:35 await 后面的逻辑 => TestAsync 最后一句
上面这段代码,可以看出async 和await 的具体效果: 主函数直接调用一个用async声明的函数(不在前面加await),运行到async声明的函数中的await后立即返回主函数,不会阻塞主函数的代码块。这个函数返回的task,一般都会和一个正在后台线程相关。
这里需要注意一点,先看代码:
static Task<string> GetReturnResult() { Console.WriteLine("执行Task.Run之前, 线程ID:{0}", Thread.CurrentThread.ManagedThreadId); var task = new Task<string>(() => { Console.WriteLine("并行线程:GetReturnResult()方法里面线程ID: {0}", Thread.CurrentThread.ManagedThreadId); Console.WriteLine("并行线程:running...."); Thread.Sleep(3000); return "我是返回值"; }); //用new创建的task对象,必须使用start方法开始任务,不然task不会开始,在这里就会会永久阻塞程序。 task.Start(); return task; /* return Task.Run(() => { Console.WriteLine("并行线程:GetReturnResult()方法里面线程ID: {0}", Thread.CurrentThread.ManagedThreadId); Console.WriteLine("并行线程:running...."); Thread.Sleep(3000); return "我是返回值"; }); */ }
虽然这个函数我们仅仅需要Task<string>,但是其实一个task必须要start后,才可能被启动执行。而Task.Run 是自动调用start方法的。
这里再帖一份代码,是使用owin中间件进行重定向的代码,其中有3种对task的使用方法:
app.MapWhen((r) => !r.Request.Path.Value.ToLowerInvariant().StartsWith("/api") && !r.Request.Path.Value.ToLowerInvariant().StartsWith("/swagger") && !r.Request.Path.Value.ToLowerInvariant().Equals("/") && !r.Request.Path.Value.ToLowerInvariant().Equals("") , spa => { spa.Run(context => { //first //context.Response.Redirect("/people");
//虽然是同步逻辑,但是需要一个异步结构的返回值,使用Task.FromResult<>可以构造 //return Task.FromResult<int>(0); //second //return Task.Run(() => context.Response.Redirect("/people")); //third var task = new Task(() => { context.Response.Redirect("/people"); }); //注意,必须手动启动!!!!!!! task.Start(); return task; }); });