其实搞成一个状态机的目的主要还是考虑到可能存在多个await的情况。对于只有1个await的情况其实状态机的必要性不大,几个if也就够了,下面扩展下上面的例子看看有2个以上await(1个和2个await的状态机都是使用if/else解决问题,从3个起开始不同)时编译器产生的代码,首先是扩展后的C#代码(以WPF应用为例):
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private async void Button_Click(object sender, RoutedEventArgs e) { var userService = new Service(); Debug.Write(Thread.CurrentThread.ManagedThreadId); var avatar = await userService.GetUserAvatarAsync(1); Debug.Write(Thread.CurrentThread.ManagedThreadId); //使用获取的avatar } } public class Service { private readonly Repository _repository; private readonly WebHepler _webHelpler; private readonly ImageLib _imgLib; public Service() { _repository = new Repository(); _webHelpler = new WebHepler(); _imgLib = new ImageLib(); } public async Task<byte[]> GetUserAvatarAsync(int id) { Debug.WriteLine("Service--" + Thread.CurrentThread.ManagedThreadId); var user = await _repository.GetByIdAsync(id); Debug.WriteLine("Service--" + Thread.CurrentThread.ManagedThreadId); var email = user.Email; var avatar = await _webHelpler.GetAvatarByEmailAsync(email); Debug.WriteLine("Service--" + Thread.CurrentThread.ManagedThreadId); var thumbnail = await _imgLib.GetImgThumbnailAsync(avatar); return thumbnail; } } public class Repository { private readonly DbContext _dbContext; private readonly DbSet<User> _set; public Repository() { //_dbContext = new DbContext(""); //_set = _dbContext.Set<User>(); } public async Task<User> GetByIdAsync(int id) { Debug.WriteLine("Repo--" + Thread.CurrentThread.ManagedThreadId); //IO... var user = await _set.FindAsync(id); Debug.WriteLine("Repo--" + Thread.CurrentThread.ManagedThreadId); return user; } } public class WebHepler { private readonly HttpClient _httpClient; public WebHepler() { _httpClient = new HttpClient(); } public async Task<byte[]> GetAvatarByEmailAsync(string email) { Debug.WriteLine("Http--" + Thread.CurrentThread.ManagedThreadId); var url = "http://avater-service-sample/" + email; var resp = await _httpClient.GetByteArrayAsync(url); Debug.WriteLine("Http--" + Thread.CurrentThread.ManagedThreadId); return resp; } } public class ImageLib { public async Task<byte[]> GetImgThumbnailAsync(byte[] avatar) { //模拟一个异步图像处理任务 return await Task.Run(() => { Task.Delay(500); return avatar; }); } }依然以Service类为例来分析await编译后的样子:
Service中的GetUserAvatar方法中的3个await将把函数体分割为4个异步区间,如下:
图6
编译生成的代码最主要的不同是生成的状态机变了,依旧是通过截图和注释来说一下这个新的状态机的执行情况(方便对比,注释将只标出与之前状态机不同的部分):
图7
通过上面的分析,async/await关键字背后的秘密已经清清楚楚。下面来说一下线程的问题。
线程!
关于async/await模式线程的问题,刚开始学习async/await那阵,看到很多文章,各种各样的说法,一度让我很迷惑。