原理:await和async就是一种基于编译器的异步编程功能,就是说,这两个关键字其实是c#的语法糖而已,都被编译器转换为了状态机。
那么,什么是状态机呢?
下面我们通过VS2015创建一个.Net Core MVC项目。
个人身份验证是微软已经写好的一套登录逻辑,名字叫做Identity。
我们打开AccountController来看登录方法:
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null) { ViewData["ReturnUrl"] = returnUrl; if (ModelState.IsValid) { // This doesn't count login failures towards account lockout // To enable password failures to trigger account lockout, set lockoutOnFailure: true var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false); if (result.Succeeded) { _logger.LogInformation(1, "User logged in."); return RedirectToLocal(returnUrl); } if (result.RequiresTwoFactor) { return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl, RememberMe = model.RememberMe }); } if (result.IsLockedOut) { _logger.LogWarning(2, "User account locked out."); return View("Lockout"); } else { ModelState.AddModelError(string.Empty, "Invalid login attempt."); return View(model); } } // If we got this far, something failed, redisplay form return View(model); }
我们看到
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
这一句异步调用了方法。
接下来,通过Reflector反编译查看这个方法。
[AsyncStateMachine(typeof(<Login>d__7)), DebuggerStepThrough, HttpPost, AllowAnonymous, ValidateAntiForgeryToken] public Task<IActionResult> Login(LoginViewModel model, string returnUrl = null) { <Login>d__7 stateMachine = new <Login>d__7 { <>4__this = this, model = model, returnUrl = returnUrl, <>t__builder = AsyncTaskMethodBuilder<IActionResult>.Create(), <>1__state = -1 }; stateMachine.<>t__builder.Start<<Login>d__7>(ref stateMachine); return stateMachine.<>t__builder.Task; }
我们看到这个方法前面的中括号中,有一个属性AsyncStateMachine,这个就是状态机,我们看到Login方法被编译器改成了这样。首先,new了一个
<Login>d__7类型的对象,那么这个编译器为我们生成的类是什么呢?点开查看
[CompilerGenerated] private sealed class <Login>d__7 : IAsyncStateMachine { // Fields public int <>1__state; public AccountController <>4__this; private SignInResult <>s__2; public AsyncTaskMethodBuilder<IActionResult> <>t__builder; private TaskAwaiter<SignInResult> <>u__1; private SignInResult <result>5__1; public LoginViewModel model; public string returnUrl; // Methods public <Login>d__7(); private void MoveNext(); [DebuggerHidden] private void SetStateMachine(IAsyncStateMachine stateMachine); }
我们看到这个类继承自接口IAsyncStateMachine,这个接口有两个方法
[__DynamicallyInvokable] public interface IAsyncStateMachine { // Methods [__DynamicallyInvokable] void MoveNext(); [__DynamicallyInvokable] void SetStateMachine(IAsyncStateMachine stateMachine); }
这两个方法就是一个典型的状态机定义:执行下一步和设置状态。状态机的工作过程如下:
一个有1和2两个有效状态的状态机,如果状态值为1,调用MoveNext时状态机会执行操作A同时将状态值改为2;如果状态值为2,调用MoveNext时状态机会执行操作B同时将状态值改为3;如果状态值为3,调用MoveNext时状态机不执行任何操作或抛出异常。
异步编程就是调用这种模式,通过编译器对代码进行重组。将一个await调用前和调用后执行的代码分配到状态机的两个状态中去执行。如果一个函数中有两个await调用,那么编译器就会生成三个状态。
await必然要调用启用了新线程的函数,那么我们怎么定义这样一种函数呢,我们可以通过task来定义,比如:
public async Task<IActionResult> EditUser() { var user = await GetCurrentUserAsync(); return View(user); } private Task<ApplicationUser> GetCurrentUserAsync() { return _userManager.GetUserAsync(HttpContext.User); }
综上所述,正如文章开头提到的,Async和Await这两个关键字使得用户能以一种简洁直观的方式实现异步编程,用户甚至不需要改变代码的逻辑结构就能将原来的同步函数改造为异步函数。
Async和Await这两个关键字由编译器转换为状态机,通过System.Threading.Tasks中的类实现异步编程。