• 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; }); });
  • 相关阅读:
    最全负载均衡:算法、实现、亿级负载解决方案详解
    淘宝分布式架构演变案例详解
    分布式一致性协议实现原理
    ReentrantReadWriteLock的使用
    线程之单例
    线程的优先级
    java线程的6种状态
    mybatis <foreach> 标签
    java多线程 上下文切换
    docker的复制和挂载
  • 原文地址:https://www.cnblogs.com/breezemist/p/9239156.html
Copyright © 2020-2023  润新知