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; }); });