天天看點

ASP.NET Core Web 應用程式系列(四)- ASP.NET Core 異步程式設計之async await

本系列将和大家分享下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,看看是否正常

ASP.NET Core Web 應用程式系列(四)- ASP.NET Core 異步程式設計之async await

可以看出是正常的

下面我們示範一下什麼時候需要用到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

版權聲明:如有雷同純屬巧合,如有侵權請及時聯系本人修改,謝謝!!!

繼續閱讀