原来是这样的async/await

好久没写技术方面的博客,,感觉近挺长一段时间都还挺忙的(虽然也不知道具体忙了点啥)。。。

最近心血来潮的想起来一个问题,在我原本的理解中,async/await在程序运行过程中主要起到一个把一个同步流程的代码异步化的作用(这么说好像也没错)。

大概就是说,原本按顺序执行1>2>3>4>5,如果在3这里添加了await,那么顺序就是线程1:1>2>3,线程2:4>5。会这么想的原因大概是,之前做前端开发也比较多,当一个请求会耗时比较久,甚至可能影响到加载或者后续一些无关流程的时候,就会让这个请求异步去获取数据以确保不耽误后面的代码执行,应用在C#中大概得想法就是,当执行完了1和2来到await这里时,会产生一个新的线程去执行3,同时原来的线程会继续执行4和5。

好久以来,对这个想法基本是深信不疑的【哭脸】

最近没事的时候突然想起这个流程,细想了一下发现这完全不合理呀o(*≧▽≦)ツ┏━┓

后续的4和5完全可能要使用3流程中使用的数据,那如果说3还没执行完就先走了4和5,那岂不是要报异常出来了?

然后顺理成章的,又一个离谱的思想冒了出来:有没有可能,如果4中需要用到3中的数据,线程2就会自动等待线程1完成以后再继续执行呢?

诶?好像说得过去…但更大的问题来了

我都要等待的情况下,不使用await就一个线程执行-》等待-》执行,当我使用了await以后,就变成了线程1在等待执行,线程2在等待线程1执行结束,这不纯纯有病么?

说了这半天,那真实的await应该发生了什么呢?

async/await其实实际的作用是防止线程阻塞。并不会让你的某个方法因为异步而运行的更快。

那么,什么是线程阻塞呢?这就好比一个服务员,帮第一桌客人点好菜以后,站在了厨房门,等着出菜,等菜出来以后再端给第一桌客人,以上完成后再去第二桌客人那边给客人点菜...服务员站着什么都不做,等待厨房出菜的这段时间,就是服务员的阻塞时间。

正确的方案应该是,服务员在提交完菜单后,应该第一时间发现第二桌要点菜的客人并为他们去点菜,而厨房出餐后应该有一个空闲的服务员(可以是原来的那个,也可以是其他的某一个)把餐端上去给第一桌的客人。

在这个正确的(非阻塞的)流程中,对于第一桌的客人来说,体验和不正确的(阻塞的)流程并不存在任何不同(微乎其微),但对于剩下的没有被安排到服务员的客人来说,那体验就大大的提升了!而这才是async/await真正发挥的作用。

回到代码,Thread.Sleep(3000)这句话会让线程停止3秒,这3秒对于线程来说那就是什么都不干了,干等3秒。而当我们使用了await Task.Delay(3000)时,线程则会挂起,然后自动释放回到线程池(线程池这个概念今天不讲了),这个时候,这个已经被释放了的线程就可以去处理其他的事情了。

说到这里就会有小聪明好奇了:不对呀,线程都被释放掉了,那我后续的代码怎么执行呢?

这里就需要提到更深入一些的内容了——async/await的本质,状态机及任务调度。

当程序执行到await Task.Delay(3000)的时候,线程会被挂起,同时状态机中会分配一个3秒的定时任务出去,await后续的代码将会被作为这个定时任务的回调来触发,挂起的线程被释放回到线程池中等待被其他地方调用。当定时任务结束后,程序池中另一个线程被调过来执行后续的回调操作(非强上下文关系)。

啊啊啊,又有大聪明好奇了,你这里写了Delay(3000)所以你知道定时任务3秒,我要不是Delay呢?要是IO或者数据库交互或者文件读取怎么办?总不能猜一个时间给定时任务吧?

嗯,说到这里,那就需要说到async/await的嵌套和执行了。

大家写代码的时候应该都会发现,最外层有await的调用,这只是说明这个方法定义的时候返回的是Task类型的可等待的任务,而实际需不需要真的执行到等待的操作还需要看到最底层里是不是真的有交互需要进行等待。

这句话怎么理解呢?就比如说你写了一个await getNumA(),结果在getNumA里写的实现只有一句return 1,那么实际上就并没有“等待”这个需求出现。

“需不需要等待”以及“等待多久”、“什么时候继续”并不是你在写方法的时候告诉状态机的,而是Task在完成时自动去触发的!

当程序运行拿到一个Task时,如果这个Task已经结束了,那么,什么都不会发生。只有当这个Task还没有结束时,后续的流程会被封装为回调,当前的线程退出(释放),后续Task完成时再获取一个线程来执行回调内容。

await的作用是再说,接下来要执行的方法,在需要的情况下,我允许它中断,等待完成后再重新找个线程来继续执行。

等等,我的线程我释放去干别的事了,那等待时间结束以后,线程池里没有线程给我用,那我岂不是要干等着了?

是,也不是,我的朋友。怎么理解呢?说是,是因为当真的出现线程池里没有线程可用的情况是,你后续的回调,确实就需要等着了,说不是,是因为,真要有线程池里没有线程可用的那一天,你的代码就该回炉重造一遍了…

那么在了解了async/await真正的作用之后,聪明的你又想要发问了:既然是这样,那我把所有方法都写成async/await那不就完美了?

其实并不是这样。async/await确实好用,但它并不是C#生来支持的,在2012年以前(.Net 4.5前)并不支持这样的异步写法,所有的流程都是同步的。为了兼容以前的内容及写作习惯,同步的方法和代码是需要存在的。

另外,async/await的使用并不是“免费”的,当你释放线程的时候,往往意味着你需要有额外的开销为状态机及定时任务、回调等服务,当你的项目不需要考虑并发的时候(如一些WPF项目winfrom项目),同步其实才是你最好的选择。

从标准上来说,微软也规定库的开发需要支持和提供同步及异步两种方法,所以

async/await虽好,但也不要滥用哦

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值