浅谈async/await

小明用async/await写了几年的异步方法,但总没有完全理解里面的机制,他决定去请教邻居小花。

小花听了小明的描述后说:首先你要明白异步的根本是什么?大白话解释异步就是:拉一个人(线程)帮着做一些耗时的事(下载、读写数据库等),我先做别的事了(退出线程),等做好了和我说下,我再继续做后面的事(恢复上下文)。

小花看到小时还没有明白,就说:我举个简单例子帮你理解吧,假如有两个方法A和B,A调用B方法,B方法是一个异步方法,这时A不等待B执行完,如图:

现在两个方法被分隔几个小块,await关键字其实就用来隔开同步和异步,上面的方法执行流程如下:

小明好像听懂了一些说:现在A方法调用DoSomethingAsync()并没有等待,如果A方法需要这个方法执行完才能继续执行,是不是要在DoSomethingAsync()前面加上await?小花回答是,并说:方法只要遇到await,就会把后面的方法给新线程执行,然后线程退出去执行别的方法,等新线程执行完后再通知当前线程恢复上下文继续执行,如图:

小明又问:你说异步方法执行完后,后面的方法会在原来的线程中恢复并执行,如果我还想在新线程中继续执行剩下的代码,要怎么办呢?小花说问的好,await调用新线程执行耗时操作时默认会捕获当前上下文,如果不想捕获,则可以调用ConfigAwait(false)方法,如图:

执行流程如下:

小花补充到,上线提到的线程1、线程2、线程3等不一定准确,因为异步的回调是使用线程池中的线程,所以回调有可能还在原来线程中执行,这个主要看操作系统的调度。

小明满意的点点头又问:我经常听同事说用异步方法会死锁,这又是为什么呢?小花听了说,他们肯定是在调用异步方法的时候使用.Result(),如图:

小明点了点头又问:那要怎么避免呢?小花说出现这种情况也和框架有关,像WinForm为了让所有UI操作都在主线程中执行,就添加了一个SynchronizationContext类实例用以表示当前上下文,而像控制台等项目这个SynchronizationContext实例默认为null,所以即使使用.Result也不会死锁。但最好使用异步的时候不要用.Result,可以使用ConfigAwait(false)指明不捕获上下文,或所有的方法全部异步到底。

 

小明听完满意地回到自己的隔间。

 

更多精彩,请关注我的公众号: