zoukankan      html  css  js  c++  java
  • ASP.NET Core Web 应用程序系列(四)- ASP.NET Core 异步编程之async await

    PS:异步编程的本质就是新开任务线程来处理。

    约定:异步的方法名均以Async结尾。

    实际上呢,异步编程就是通过Task.Run()来实现的。

    了解线程的人都知道,新开一个线程来处理事务这个很常见,但是在以往是没办法接收线程里面返回的值的。所以这时候就该await出场了,await从字面意思不难理解,就是等待的意思。

    执行await的方法必须是async修饰的,并且是Task的类型。 异步执行后,返回的信息存储在result属性中。但并非主进程就会卡在await行的代码上,执行到await方法之后主线程继续往下执行,无需等待新的线程执行完再继续。只有当需要用到新线程返回的result结果时,此时主进程才会等待新线程执行完并返回内容。也就是说,若无需用到新线程返回的结果,那么主进程不会等待。

    async和await呢,返回类型就3种:void、Task、Task<TResult>。

    1、void

    如果在触发后,你懒得管,请使用 void。

    void返回类型主要用在事件处理程序中,一种称为“fire and forget”(触发并忘记)的活动的方法。除了它之外,我们都应该尽可能是用Task,作为我们异步方法的返回值。

    返回void,意味着不能await该异步方法,即可能出现线程阻塞,并且也无法获取exception抛出的异常,通常这些异常会导致我们的程序失败,如果你使用的是Task和Task<TResult>,catch到的异常会包装在属性里面,调用方法就可以从中获取异常信息,并选择正确的处理方式。

    2、Task

    你如果只是想知道执行的状态,而不需要一个具体的返回结果时,请使用Task。
    与void对比呢,Task可以使用await进行等待新线程执行完毕。而void不需要等待。

    3、Task<TResult> 

    当你添加async关键字后,需要返回一个将用于后续操作的对象,请使用Task<TResult>。

    主要有两种方式获取结果值,一个是使用Result属性,一个是使用await。他们的区别在于:如果你使用的是Result,它带有阻塞性,即在任务完成之前进行访问读取它,当前处于活动状态的线程都会出现阻塞的情形,一直到结果值可用。所以,在绝大多数情况下,除非你有绝对的理由告诉自己,否则都应该使用await,而不是属性Result来读取结果值。

    接下来我们来看个例子,在上一章的基础上我们添加异步的方法。

    首先是仓储层接口:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Threading.Tasks;
    
    using TianYa.DotNetShare.Model;
    
    namespace TianYa.DotNetShare.Repository
    {
        /// <summary>
        /// 学生类仓储层接口
        /// </summary>
        public interface IStudentRepository
        {
            /// <summary>
            /// 根据学号获取学生信息
            /// </summary>
            /// <param name="stuNo">学号</param>
            /// <returns>学生信息</returns>
            Student GetStuInfo(string stuNo);
    
            /// <summary>
            /// 根据学号获取学生信息(异步)
            /// </summary>
            /// <param name="stuNo">学号</param>
            /// <returns>学生信息</returns>
            Task<Student> GetStuInfoAsync(string stuNo);
        }
    }

    接着是仓储层实现:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Threading.Tasks;
    
    using TianYa.DotNetShare.Model;
    
    namespace TianYa.DotNetShare.Repository.Impl
    {
        /// <summary>
        /// 学生类仓储层
        /// </summary>
        public class StudentRepository : IStudentRepository
        {
            /// <summary>
            /// 根据学号获取学生信息
            /// </summary>
            /// <param name="stuNo">学号</param>
            /// <returns>学生信息</returns>
            public Student GetStuInfo(string stuNo)
            {
                //数据访问逻辑,此处为了演示就简单些
                var student = new Student();
                switch (stuNo)
                {
                    case "10000":
                        student = new Student() { StuNo = "10000", Name = "张三", Sex = "", Age = 20 };
                        break;
                    case "10001":
                        student = new Student() { StuNo = "10001", Name = "钱七七", Sex = "", Age = 18 };
                        break;
                    case "10002":
                        student = new Student() { StuNo = "10002", Name = "李四", Sex = "", Age = 21 };
                        break;
                    default:
                        student = new Student() { StuNo = "10003", Name = "王五", Sex = "", Age = 25 };
                        break;
                }
    
                return student;
            }
    
            /// <summary>
            /// 根据学号获取学生信息(异步)
            /// </summary>
            /// <param name="stuNo">学号</param>
            /// <returns>学生信息</returns>
            public virtual async Task<Student> GetStuInfoAsync(string stuNo)
            {
                return await Task.Run(() => this.GetStuInfo(stuNo));
            }
        }
    }

    然后是服务层接口:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Threading.Tasks;
    
    using TianYa.DotNetShare.Model;
    
    namespace TianYa.DotNetShare.Service
    {
        /// <summary>
        /// 学生类服务层接口
        /// </summary>
        public interface IStudentService
        {
            /// <summary>
            /// 根据学号获取学生信息
            /// </summary>
            /// <param name="stuNo">学号</param>
            /// <returns>学生信息</returns>
            Student GetStuInfo(string stuNo);
    
            /// <summary>
            /// 根据学号获取学生信息(异步)
            /// </summary>
            /// <param name="stuNo">学号</param>
            /// <returns>学生信息</returns>
            Task<Student> GetStuInfoAsync(string stuNo);
        }
    }

    再接着是服务层实现:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Threading.Tasks;
    
    using TianYa.DotNetShare.Model;
    using TianYa.DotNetShare.Repository;
    
    namespace TianYa.DotNetShare.Service.Impl
    {
        /// <summary>
        /// 学生类服务层
        /// </summary>
        public class StudentService : IStudentService
        {
            /// <summary>
            /// 定义仓储层学生抽象类对象
            /// </summary>
            protected IStudentRepository StuRepository;
    
            /// <summary>
            /// 空构造函数
            /// </summary>
            public StudentService() { }
    
            /// <summary>
            /// 构造函数
            /// </summary>
            /// <param name="stuRepository">仓储层学生抽象类对象</param>
            public StudentService(IStudentRepository stuRepository)
            {
                this.StuRepository = stuRepository;
            }
    
            /// <summary>
            /// 根据学号获取学生信息
            /// </summary>
            /// <param name="stuNo">学号</param>
            /// <returns>学生信息</returns>
            public Student GetStuInfo(string stuNo)
            {
                var stu = StuRepository.GetStuInfo(stuNo);
                return stu;
            }
    
            /// <summary>
            /// 根据学号获取学生信息(异步)
            /// </summary>
            /// <param name="stuNo">学号</param>
            /// <returns>学生信息</returns>
            public virtual async Task<Student> GetStuInfoAsync(string stuNo)
            {
                return await StuRepository.GetStuInfoAsync(stuNo);
            }
        }
    }

    最后进行控制器中的调用:

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Mvc;
    using TianYa.DotNetShare.CoreAutofacDemo.Models;
    
    using TianYa.DotNetShare.Service;
    using TianYa.DotNetShare.Repository;
    using TianYa.DotNetShare.Repository.Impl;
    
    namespace TianYa.DotNetShare.CoreAutofacDemo.Controllers
    {
        public class HomeController : Controller
        {
            /// <summary>
            /// 定义仓储层学生抽象类对象
            /// </summary>
            private IStudentRepository _stuRepository;
    
            /// <summary>
            /// 定义服务层学生抽象类对象
            /// </summary>
            private IStudentService _stuService;
    
            /// <summary>
            /// 定义服务层学生抽象类对象
            /// 属性注入:访问修饰符必须为public,否则会注入失败。
            /// </summary>
            public IStudentService StuService { get; set; }
    
            /// <summary>
            /// 定义仓储层学生实现类对象
            /// 属性注入:访问修饰符必须为public,否则会注入失败。
            /// </summary>
            public StudentRepository StuRepository { get; set; }
    
            /// <summary>
            /// 通过构造函数进行注入
            /// 注意:参数是抽象类,而非实现类,因为已经在Startup.cs中将实现类映射给了抽象类
            /// </summary>
            /// <param name="stuRepository">仓储层学生抽象类对象</param>
            /// <param name="stuService">服务层学生抽象类对象</param>
            public HomeController(IStudentRepository stuRepository, IStudentService stuService)
            {
                this._stuRepository = stuRepository;
                this._stuService = stuService;
            }
    
            public IActionResult Index()
            {
                var stu1 = StuRepository.GetStuInfo("10000");
                var stu2 = StuService.GetStuInfo("10001");
                var stu3 = _stuService.GetStuInfo("10002");
                var stu4 = _stuRepository.GetStuInfo("1003");
                string msg = $"学号:10000,姓名:{stu1.Name},性别:{stu1.Sex},年龄:{stu1.Age}<br />";
                msg += $"学号:10001,姓名:{stu2.Name},性别:{stu2.Sex},年龄:{stu2.Age}<br/>";
                msg += $"学号:10002,姓名:{stu3.Name},性别:{stu3.Sex},年龄:{stu3.Age}<br/>";
                msg += $"学号:10003,姓名:{stu4.Name},性别:{stu4.Sex},年龄:{stu4.Age}<br/>";
    
                return Content(msg, "text/html", System.Text.Encoding.UTF8);
            }
    
            public async Task<IActionResult> Privacy()
            {
                var stu = await _stuService.GetStuInfoAsync("10000");
                return Json(stu);
            }
    
            [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
            public IActionResult Error()
            {
                return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
            }
        }
    }

    至此完成处理,我们来访问一下/home/privacy,看看是否正常

    可以看出是正常的

    下面我们演示一下什么时候需要用到result属性:

    //用了await则不需要Result属性
    public async Task<IActionResult> Privacy()
    {
        var stu = await _stuService.GetStuInfoAsync("10000");
        return Json(stu);
    }
    //没有用await则需要Result属性
    public async Task<IActionResult> Privacy()
    {
        var stu = _stuService.GetStuInfoAsync("10000").Result;
        return Json(stu);
    }

    此外需要特别注意的是:在异步使用过程中,若无返回值时应使用Task来代替void

    /// <summary>
    /// 无返回值时应使用Task来代替void
    /// </summary>
    /// <param name="stuNo">学号</param>
    public async Task SaveLogAsync(string stuNo)
    {
        await StuRepository.GetStuInfoAsync(stuNo);
        // TODO
    }

    至此我们的异步编程就讲解完了。

    总结:

    1、尽量优先使用Task<TResult>和Task作为异步方法的返回类型。

    2、如果用了await则方法必须使用async来修饰,并且是Task的类型。

    demo源码:

    链接:https://pan.baidu.com/s/1Wb0Mebm-nh9YFOaYNLwO-g 
    提取码:1ayv

    参考博文:https://www.cnblogs.com/fei686868/p/9637310.html

    版权声明:如有雷同纯属巧合,如有侵权请及时联系本人修改,谢谢!!!

  • 相关阅读:
    搭建DG(data guard),及搭建过程中遇到的一些小问题 高伟
    介绍linux下vi命令的使用
    linux gcc编译器使用
    Linux进程编程介绍
    事件与接口实例讲解 C#
    Linux 2.6内核的编译步骤及模块的动态加载
    C# 各种定时器比较 zz
    linux下增加系统调用
    VirtualBox共享文件夹
    C++程序的单元测试(转贴)
  • 原文地址:https://www.cnblogs.com/xyh9039/p/11391507.html
Copyright © 2011-2022 走看看