bennyhuo / Bennyhuo

bennyhuo.vercel.app
8 stars 3 forks source link

我写了一本书,《深入理解 Kotlin 协程》 | Bennyhuo #42

Open bennyhuo opened 4 years ago

bennyhuo commented 4 years ago

https://www.bennyhuo.com/2020/06/23/understanding-kotlin-coroutines/

没想到 Kotlin 的协程居然会成为一个有争议的话题,谁让官方材料太少呢。

kylooh commented 4 years ago

刚刚看完第一章,趁脑子热给点读书反馈。第一章写上有点让人读天书的感觉。感觉就是没法跟着作者的思路走。这里比较下第二章的一句话——“协程的核心概念就是函数或者一段程序能够被挂起,稍后再在挂起的位置恢复”。这里明确了要谈论的问题,我觉得这种能够让我看的时候,更清楚我在看什么。提出问题——设计方案——改进方案的模式,对我更加舒服一点。 第二个问题点是,第一章使用了太多的代码,需要有异步基础才能看懂,起不到太大的引人入胜的作用。而且举的例子不够抽象,没办法联系到实际,不便于理解。之前在阿里云的论坛上看到了一个用开餐厅解释akka的actor模型的文章,我还是比较适应这种的。(:з」∠) 最后用我自己的话总结下第一章的内容。第一章主要是想解释如何实现异步,以及异步出现的问题。首先要弄清楚为什么异步,异步的主要目的是解决部分代码执行时间过长的问题,缩短程序执行时间。当我们完成某一项任务时,我们可以把任务拆分成多个子任务,对于没有顺序执行关系的平行任务,我们可以让他们同时执行。首先引入的是thread概念,thread可以在当前线程上开启新的线程,我们只需要告诉程序,任务完成后需要执行的响应操作。但我们有新的问题,如何让两个平行任务的结果合并到一个输出,即如何获取异步任务的结果。这里对应promise概念,即能够实现异步任务状态查询,可以让程序挂起并恢复。以及future等高级工具,能够实现异步任务的状态查询,结果合并,异常处理,阻塞式获取结果等复杂场景。番外就是对于可能出现多种情况的回调,需要设计多种方案,使用tryCatch、onError的方案设计。 这里我推荐比较下程序出入栈的概念,可以帮助理解挂起和恢复现场的概念。我说的可能不对,各位有问题也欢迎指出来。

bennyhuo commented 4 years ago

感谢反馈

--------------原始邮件-------------- 发件人:"70 "<notifications@github.com>; 发送时间:2020年6月28日(星期天) 下午4:15 收件人:"enbandari/Bennyhuo" <Bennyhuo@noreply.github.com>; 抄送:"Bennyhuo "<bennyhuo@kotliner.cn>;"Author "<author@noreply.github.com>; 主题:Re: [enbandari/Bennyhuo] 我写了一本书,《深入理解 Kotlin 协程》 | Bennyhuo (#42)

刚刚看完第一章,趁脑子热给点读书反馈。第一章写上有点让人读天书的感觉。感觉就是没法跟着作者的思路走。这里比较下第二章的一句话——“协程的核心概念就是函数或者一段程序能够被挂起,稍后再在挂起的位置恢复”。这里明确了要谈论的问题,我觉得这种能够让我看的时候,更清楚我在看什么。提出问题——设计方案——改进方案的模式,对我更加舒服一点。 第二个问题点是,第一章使用了太多的代码,需要有异步基础才能看懂,起不到太大的引人入胜的作用。而且举的例子不够抽象,没办法联系到实际,不便于理解。之前在阿里云的论坛上看到了一个用开餐厅解释akka的actor模型的文章,我还是比较适应这种的。(:з」∠) 最后用我自己的话总结下第一章的内容。第一章主要是想解释如何实现异步,以及异步出现的问题。首先要弄清楚为什么异步,异步的主要目的是解决部分代码执行时间过长的问题,缩短程序执行时间。当我们完成某一项任务时,我们可以把任务拆分成多个子任务,对于没有顺序执行关系的平行任务,我们可以让他们同时执行。首先引入的是thread概念,thread可以在当前线程上开启新的线程,我们只需要告诉程序,任务完成后需要执行的响应操作。但我们有新的问题,如何让两个平行任务的结果合并到一个输出,即如何获取异步任务的结果。这里对应promise概念,即能够实现异步任务状态查询,可以让程序挂起并恢复。以及future等高级工具,能够实现异步任务的状态查询,结果合并,异常处理,阻塞式获取结果等复杂场景。番外就是对于可能出现多种情况的回调,需要设计多种方案,使用tryCatch、onError的方案设计。 这里我推荐比较下程序出入栈的概念,可以帮助理解挂起和恢复现场的概念。我说的可能不对,各位有问题也欢迎指出来。

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or unsubscribe.

kylooh commented 4 years ago

差点就没看懂,说实话一开始看着就一脸问号,不知道在说什么,不过看到后面实现部分,就明白前面大概要讲什么了。本章重点:“协程需要关注的就是程序如何处理自己的挂起和恢复”,“分别按照栈的有无和有无调度权转移的对称性进行了分类”(这里其实写的有点啰嗦,可以去掉进行和重复的有无) 我又来写读书笔记了,第二章解释了如何实现协程的挂起和恢复。首先是挂起,比较了调用栈中的保存现场和恢复,给协程分类成无栈和有栈两种实现。提出来一个核心概念,调度权移交。这里我的理解是,协程可以简化成如何实现一个及时评估的iterator。python的generator有点没懂,实现方式应该是带yield函数返回的其实是generator,并评估方法是在yield标志位结束,直到下一个next(求证)。lua实现的更蒙,这里应该是实现了一种便捷的线程切换,每次需要评估的时候,切换到另一个线程去求值(感觉求值完毕后应该是保留了现场,crountine就是用于保留现场的上下文,类似于一个变量容器,在不同thread中传递),然后再切换回来(求证)。Go的channel实现反而比较容易理解,这里可以对比rx的实现,都是从第三方流里面去评估对象(但是具体如何切换线程就不清楚了)。前两种的调度权很明显都是在调用方法内的,是非对称实现。第三种实现涉及到了独立的channel对象,这里的调度权可以用读写锁来理解。而且由于channel是独立的,这里存在一个调度器,通过调度器来分配调度权,是前面提到的对称有栈协程实现。 果然我还是太菜_(:з」∠)_不敢开口提意见了

bennyhuo commented 4 years ago

兄弟们把保护打到公屏上;-)

--------------原始邮件-------------- 发件人:"70 "<notifications@github.com>; 发送时间:2020年6月29日(星期一) 上午10:14 收件人:"enbandari/Bennyhuo" <Bennyhuo@noreply.github.com>; 抄送:"Bennyhuo "<bennyhuo@kotliner.cn>;"Author "<author@noreply.github.com>; 主题:Re: [enbandari/Bennyhuo] 我写了一本书,《深入理解 Kotlin 协程》 | Bennyhuo (#42)

差点就没看懂,说实话一开始看着就一脸问号,不知道在说什么,不过看到后面实现部分,就明白前面大概要讲什么了。本章重点:“协程需要关注的就是程序如何处理自己的挂起和恢复”,“分别按照栈的有无和有无调度权转移的对称性进行了分类”(这里其实写的有点啰嗦,可以去掉进行和重复的有无) 我又来写读书笔记了,第二章解释了如何实现协程的挂起和恢复。首先是挂起,比较了调用栈中的保存现场和恢复,给协程分类成无栈和有栈两种实现。提出来一个核心概念,调度权移交。这里我的理解是,协程可以简化成如何实现一个及时评估的iterator。python的generator有点没懂,实现方式应该是带yield函数返回的其实是generator,并评估方法是在yield标志位结束,直到下一个next(求证)。lua实现的更蒙,这里应该是实现了一种便捷的线程切换,每次需要评估的时候,切换到另一个线程去求值(感觉求值完毕后应该是保留了现场,crountine就是用于保留现场的上下文,类似于一个变量容器,在不同thread中传递),然后再切换回来(求证)。Go的channel实现反而比较容易理解,这里可以对比rx的实现,都是从第三方流里面去评估对象(但是具体如何切换线程就不清楚了)。前两种的调度权很明显都是在调用方法内的,是非对称实现。第三种实现涉及到了独立的channel对象,这里的调度权可以用读写锁来理解。而且由于channel是独立的,这里存在一个调度器,通过调度器来分配调度权,是前面提到的对称有栈协程实现。 果然我还是太菜_(:з」∠)_不敢开口提意见了

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or unsubscribe.

kylooh commented 4 years ago

呜呜呜,昨天没有怎么看,来晚了。第三章是实战篇,直接看还是有点困难,我翻了翻源码。本章讲了如何实现简单的协程,我用自己方式整理一下。首先,我们可以从样本代码出发,协程是一个Continuation对像,通过resume方式恢复运行。SafeContinuation的resumeWith会对协程的状态检查并更新,成功后会传递结果给被代理的协程,即我们写的处理函数。第一次启动协程的时候,resume函数传递了一个Unit,作为协程的启动参数(这是无参类型的启动方式,通过设置类型为Unit来实现的)在启动的时候,检查当前状态,启用被代理的协程。

现在问题变成了createCoroutine是如何把两个协程合并成一个的,即我们现在被代理的对象。实际上就是简单的组合在了一起。里面实现了一个RestrictedContinuationImpl,启动方法invokeSuspend里面,在BaseContinuationImpl声明了运行流程,这里是我们的入口函数,负责拼接两个协程。到目前为止,我们发现协程和普通的函数似乎并没有太大的区别,里面有很多保证原子性和资源锁竞争的代码,保证了多线程情况下能够正确执行。

以上部分都和书上总结的一致。协程就是一个Continuation,个带context回调函数。

等等,为什么没看到任何异步的操作?协程不就是个带状态的函数吗,异步呢?benny做了解答:“异步调用是否发生,取决于resume函数与对应的挂起函数嗲用是否存在相同的调用栈上”。所以,现在我们已经有了包含状态的函数(类比future工具),剩下的就是什么时候用什么方式启动它了。

本章还有一个点,叫做拦截器。前面我们其实漏掉了一部分。之前生成的协程,是通过intercept函数,进行了一次异步包装。是拿到ContinuationInterceptor包装成DispatchedTask,是协程能够异步的基础,里面定义了异步需要的cancel等异步方法,而且内置一个dispatcher。其他的我还窥到了CoroutineStackFrame,看定义应该是一个异步调用栈。

回到书上介绍的,intercepter可以实现对continuation的拦截和释放,实现方式就是包装代理,异步的实现,也是通过代理让实际的协程在分发器中运行。这里有个有趣的点,在DispatchedTask中,协程和代理有一个相互代理的关系,他们是互相持有的。这里猜测下是用来做恢复的。

本章的内容感觉不多,但是对于不熟悉协程的人来说,比较生疏。呜呜呜,我好菜啊。希望各位大佬帮我指出问题,看我理解的对不对。

bennyhuo commented 4 years ago

你看得还挺快

--------------原始邮件-------------- 发件人:"70 "<notifications@github.com>; 发送时间:2020年7月1日(星期三) 晚上6:46 收件人:"enbandari/Bennyhuo" <Bennyhuo@noreply.github.com>; 抄送:"Bennyhuo "<bennyhuo@kotliner.cn>;"Author "<author@noreply.github.com>; 主题:Re: [enbandari/Bennyhuo] 我写了一本书,《深入理解 Kotlin 协程》 | Bennyhuo (#42)

呜呜呜,昨天没有怎么看,来晚了。第三章是实战篇,直接看还是有点困难,我翻了翻源码。本章讲了如何实现简单的协程,我用自己方式整理一下。首先,我们可以从样本代码出发,协程是一个Continuation对像,通过resume方式恢复运行。SafeContinuation的resumeWith会对协程的状态检查并更新,成功后会传递结果给被代理的协程,即我们写的处理函数。第一次启动协程的时候,resume函数传递了一个Unit,作为协程的启动参数(这是无参类型的启动方式,通过设置类型为Unit来实现的)在启动的时候,检查当前状态,启用被代理的协程。

现在问题变成了createCoroutine是如何把两个协程合并成一个的,即我们现在被代理的对象。实际上就是简单的组合在了一起。里面实现了一个RestrictedContinuationImpl,启动方法invokeSuspend里面,在BaseContinuationImpl声明了运行流程,这里是我们的入口函数,负责拼接两个协程。到目前为止,我们发现协程和普通的函数似乎并没有太大的区别,里面有很多保证原子性和资源锁竞争的代码,保证了多线程情况下能够正确执行。

以上部分都和书上总结的一致。协程就是一个Continuation,个带context回调函数。

等等,为什么没看到任何异步的操作?协程不就是个带状态的函数吗,异步呢?benny做了解答:“异步调用是否发生,取决于resume函数与对应的挂起函数嗲用是否存在相同的调用栈上”。所以,现在我们已经有了包含状态的函数(类比future工具),剩下的就是什么时候用什么方式启动它了。

本章还有一个点,叫做拦截器。前面我们其实漏掉了一部分。之前生成的协程,是通过intercept函数,进行了一次异步包装。是拿到ContinuationInterceptor包装成DispatchedTask,是协程能够异步的基础,里面定义了异步需要的cancel等异步方法,而且内置一个dispatcher。其他的我还窥到了CoroutineStackFrame,看定义应该是一个异步调用栈。

回到书上介绍的,intercepter可以实现对continuation的拦截和释放,实现方式就是包装代理,异步的实现,也是通过代理让实际的协程在分发器中运行。这里有个有趣的点,在DispatchedTask中,协程和代理有一个相互代理的关系,他们是互相持有的。这里猜测下是用来做恢复的。

本章的内容感觉不多,但是对于不熟悉协程的人来说,比较生疏。呜呜呜,我好菜啊。希望各位大佬帮我指出问题,看我理解的对不对。

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or unsubscribe.

docwei2050 commented 4 years ago

老师炫技的部分太多了,其实我只关心flutter的async await和kotlin的协程,其他go python的我不关心啊,哈哈!!!

bennyhuo commented 4 years ago

老师炫技的部分太多了,其实我只关心flutter的async await和kotlin的协程,其他go python的我不关心啊,哈哈!!!

深入理解嘛,只给你讲 async await 怕是连门槛都看不到~

kylooh commented 1 year ago

上次把benny的书放下后,突然想起,又看了一眼,感觉知道为什么上次没能读下去了。感觉很多表达不够自然,或者说目的不是很明显,让人无法下意识去抓住作者的表达欲望。

举个例子,开篇的“如果我们把处理器比作大脑,那么程序的执行就是处理器对指令进行阅读理解......也不过是从这一段程序跳到另一段程序,接着执行”,这一段其实就有点看不出来要表达什么,我觉得可能是想表达程序是顺序执行的。

我想了下,“如同人类阅读文章时,是逐字逐句的往下读的,倘若反着阅读,便失去了可读性,同时也无法保证文章有正确的因果和逻辑。程序也是如此,处理器就如同程序的大脑,负责顺序执行代码这篇“文章””

这样的话,是不是就会变得容易理解多了。希望benny给我指点指点

kylooh commented 1 year ago

同样,后面关于同步和异步的执行,同步的概念应该是取得“连续时间”的概念,文章中“顺序执行的情形叫做同步执行,反之称为异步执行”其实是十分正确的,但是我第一次阅读的时候,感觉并没有解释的很清楚。感觉就是,这里似乎提出了“异步”和“同步”的概念,但是似乎解释的太少,显得并不能理解这里的用意。

如果按照benny在前面提到的大脑和阅读的例子的话,异步执行,就类似于来回捡着读书,最后都是读完,但是并不是连续的。这样的话,这几个知识点就关联起来了,阅读体验会好很多。

kylooh commented 1 year ago

那么,接下来的多线程例子,就可以用“使用第三只眼读书”来比喻,可能第三只眼看的段落比“另外两只眼睛更快”,等到所有眼睛都看完了,就可以翻下一页了🤣

kylooh commented 1 year ago

代码1-5 这里出现了换页,严重影响了读代码时候的理解,建议benny以后排版的时候避免代码换页。图1-3我其实看的不是很明白,里面标记的内容和代码完全对不上。代码我能理解,图我其实看得懂,但是放在一起,我就开始怀疑自己的理解能力了。

kylooh commented 1 year ago

第7页的图1-4和1-5,这里提到了“同步化”,但是并没有和前面一样的给出代码演示,就显得很莫名其妙,不知道作者在说什么,并没有办法理解图为什么可以这么改。

同时,代码1-9提到的线程异常的情况,实际上是一段很不合理的代码,asyncBitmap里面的异常,以及checkUrl的异常,都是预期的异常,属于逻辑层面需要考虑的情况。但是异步异常并不是一个预期的异常,是系统级别的异常,即使都是直接显示异常信息,但也不应该把这两个视为同一种处理,这俩只是恰好在形式上一致了。如果出于这种这种目的合并线程异常和业务异常,我觉得是完全不合理的,多数响应式框架中,框架本身的异步异常,并不会直接被流内捕获。

我其实给不出很好的建议,唯一能想到的就似乎在thread外面加一个try-catch-throw,转换成一种业务异常。话说我这么多话,会不会让benny讨厌,呜呜呜😭第二遍看,感觉到处都是问题,我都怀疑自己是不是benny铁粉了

bennyhuo commented 1 year ago

上次把benny的书放下后,突然想起,又看了一眼,感觉知道为什么上次没能读下去了。感觉很多表达不够自然,或者说目的不是很明显,让人无法下意识去抓住作者的表达欲望。

举个例子,开篇的“如果我们把处理器比作大脑,那么程序的执行就是处理器对指令进行阅读理解......也不过是从这一段程序跳到另一段程序,接着执行”,这一段其实就有点看不出来要表达什么,我觉得可能是想表达程序是顺序执行的。

我想了下,“如同人类阅读文章时,是逐字逐句的往下读的,倘若反着阅读,便失去了可读性,同时也无法保证文章有正确的因果和逻辑。程序也是如此,处理器就如同程序的大脑,负责顺序执行代码这篇“文章””

这样的话,是不是就会变得容易理解多了。希望benny给我指点指点

先贴一下原文哈。

“如果我们把处理器比作“大脑”,那么程序的执行就是处理器对指令进行“阅读理解”的过程,任何时候处理器都是逐条执行指令(见图1-1),哪怕出现了外部中断,也不过是从这一段程序跳到另一个段程序,接着顺序执行。”

读者里面对这句话有疑问的你还是第一个。这句话简单提了一下程序是如何执行的(对指令进行“阅读理解”,逐条),抱歉哈,实话讲,我又看了看,没看出有什么意思不明确的地方。。。咋办= =、

bennyhuo commented 1 year ago

同样,后面关于同步和异步的执行,同步的概念应该是取得“连续时间”的概念,文章中“顺序执行的情形叫做同步执行,反之称为异步执行”其实是十分正确的,但是我第一次阅读的时候,感觉并没有解释的很清楚。感觉就是,这里似乎提出了“异步”和“同步”的概念,但是似乎解释的太少,显得并不能理解这里的用意。

如果按照benny在前面提到的大脑和阅读的例子的话,异步执行,就类似于来回捡着读书,最后都是读完,但是并不是连续的。这样的话,这几个知识点就关联起来了,阅读体验会好很多。

这个建议不错。

bennyhuo commented 1 year ago

那么,接下来的多线程例子,就可以用“使用第三只眼读书”来比喻,可能第三只眼看的段落比“另外两只眼睛更快”,等到所有眼睛都看完了,就可以翻下一页了🤣

多线程这个应该不需要过多解释吧。。。如果没有多线程的概念,这本书其实会很难读的

bennyhuo commented 1 year ago

代码1-5 这里出现了换页,严重影响了读代码时候的理解,建议benny以后排版的时候避免代码换页。图1-3我其实看的不是很明白,里面标记的内容和代码完全对不上。代码我能理解,图我其实看得懂,但是放在一起,我就开始怀疑自己的理解能力了。

排版代码被分开这个问题,其实基本无解,因为这些都是出版社负责的。

图 1-3 里面 alt 1/2 对应于代码 1-5 里面 when 表达式的两种情况,你再理解一下看看?你说的对不上我估计是 download 和 sendRequest 吧,这个可能是个问题,看着好像是后面重构了一次代码,图里面没改。这个可以记个勘误。

bennyhuo commented 1 year ago

第7页的图1-4和1-5,这里提到了“同步化”,但是并没有和前面一样的给出代码演示,就显得很莫名其妙,不知道作者在说什么,并没有办法理解图为什么可以这么改。

同时,代码1-9提到的线程异常的情况,实际上是一段很不合理的代码,asyncBitmap里面的异常,以及checkUrl的异常,都是预期的异常,属于逻辑层面需要考虑的情况。但是异步异常并不是一个预期的异常,是系统级别的异常,即使都是直接显示异常信息,但也不应该把这两个视为同一种处理,这俩只是恰好在形式上一致了。如果出于这种这种目的合并线程异常和业务异常,我觉得是完全不合理的,多数响应式框架中,框架本身的异步异常,并不会直接被流内捕获。

我其实给不出很好的建议,唯一能想到的就似乎在thread外面加一个try-catch-throw,转换成一种业务异常。话说我这么多话,会不会让benny讨厌,呜呜呜😭第二遍看,感觉到处都是问题,我都怀疑自己是不是benny铁粉了

第一个点,异步逻辑同步化。

图 1-4 之前有这么一段: “换个角度,异常也是函数调用结果的一种,既然asyncBitmap本身也可能抛出异常,那我们完全可以对抛出的异常与返回的结果一视同仁。如果有一些手段能帮我们把异常的处理合并,我们处理起来就会相对轻松一些。仔细对比图1-4和图1-5,同样存在异步逻辑,只不过后者的异步的调用流程通过编译器或者其他手段简化成了“同步化”的调用,因此前者需要分别处理A到B和C到D处的异常,而后者对整体流程做一次处理即可,复杂度明显降低。”

原文在图 1-5 之后:“异步逻辑同步化正是Kotlin协程要解决的问题。”

这两张图对比的就是异常的异步和同步处理,我们已经知道了异步处理异常比较麻烦,因此自然地想到有没有什么办法能够实现异步同步化。这一节其实还在引出问题,具体的解法会在下一节统一给出,所以就没有给出所谓的异步代码同步化的代码。如果给出来的话,除了影响行文思路,还会面临一个问题,这段代码要不要解释?不解释的话它为什么能异步代码同步化?

第二个点,代码 1-9 的异常问题。 这里其实没有系统异常,都是逻辑异常,checkUrl 和 asyncBitmap 调用的时候可能出现的异常例如 url 不合法,线程池拒绝等;回调里面接收的异常例如解析失败,IO 异常等。这两类异常对于业务上层来讲是一样的,都是业务逻辑的异常,实践当中绝大多数情况下处理的函数都是同一个。

这个地方的关键在于,showError 被调用了两次,除了写起来不简便之外,这还是个代码易错点,你会在实践中看到有的人一开始并没有意识到这两处代码都需要处理异常,后面又意识到了,然后用了不同的代码去做异常处理,结果就是后面两处的逻辑开始不一致,进而引发问题。

kylooh commented 1 year ago

上次把benny的书放下后,突然想起,又看了一眼,感觉知道为什么上次没能读下去了。感觉很多表达不够自然,或者说目的不是很明显,让人无法下意识去抓住作者的表达欲望。 举个例子,开篇的“如果我们把处理器比作大脑,那么程序的执行就是处理器对指令进行阅读理解......也不过是从这一段程序跳到另一段程序,接着执行”,这一段其实就有点看不出来要表达什么,我觉得可能是想表达程序是顺序执行的。 我想了下,“如同人类阅读文章时,是逐字逐句的往下读的,倘若反着阅读,便失去了可读性,同时也无法保证文章有正确的因果和逻辑。程序也是如此,处理器就如同程序的大脑,负责顺序执行代码这篇“文章”” 这样的话,是不是就会变得容易理解多了。希望benny给我指点指点

先贴一下原文哈。

“如果我们把处理器比作“大脑”,那么程序的执行就是处理器对指令进行“阅读理解”的过程,任何时候处理器都是逐条执行指令(见图1-1),哪怕出现了外部中断,也不过是从这一段程序跳到另一个段程序,接着顺序执行。”

读者里面对这句话有疑问的你还是第一个。这句话简单提了一下程序是如何执行的(对指令进行“阅读理解”,逐条),抱歉哈,实话讲,我又看了看,没看出有什么意思不明确的地方。。。咋办= =、

呜呜呜,活的benny诶~

我是站在一个稍微小白一点的角度来看的,这里“如果我们把处理器比作“大脑”“,相当于让读者带入一种假设,把处理器想象成大脑,后面提到的“阅读理解”也是比较自然的会让读者把指令处理和阅读联系按起来。后面出现了“逐条执行”,可以让读者带入逐字逐句的含义。

但是到了“外部中断”,“从一段程序跳到另一段程序”这些,就显得不是那么容易和“大脑”、”阅读“这些概念联系起来。反而是,如果我从程序执行、中断、恢复等计算机执行的角度思考,就没有任何障碍。也就是说这里的比喻没有延续,会有一种突然打算思路的感觉,短暂丢失信息上下文,需要重新组织一下思维。

因为在我的理解里,阅读是一种线性思考,有两个目的,一种是重复别人的线性思考,获取知识,第二种是在别人线性思考的基础上,结合自己的理解,拓展出更多的相关性,达到巩固和理解的目的。

大概就是这么个意思😊

kylooh commented 1 year ago

代码1-5 这里出现了换页,严重影响了读代码时候的理解,建议benny以后排版的时候避免代码换页。图1-3我其实看的不是很明白,里面标记的内容和代码完全对不上。代码我能理解,图我其实看得懂,但是放在一起,我就开始怀疑自己的理解能力了。

排版代码被分开这个问题,其实基本无解,因为这些都是出版社负责的。

图 1-3 里面 alt 1/2 对应于代码 1-5 里面 when 表达式的两种情况,你再理解一下看看?你说的对不上我估计是 download 和 sendRequest 吧,这个可能是个问题,看着好像是后面重构了一次代码,图里面没改。这个可以记个勘误。

不好意思是我没说清楚,我说的对不上是指的

  1. 出现了代码、和前文中没有的内容。比如"getUrl"、”Main"、“Async”、“Server",download 和 sendRequest 确实也是对不上的,我手上这本还有 cache。我在阅读的时候,头脑里会构建出代码的流程图,然后和实际图片对比的话,就感觉讲的不是同一件事。这段代码如果是做 android 开发的应该会比较熟悉,典型的数据请求。但是做后端的看就会显得稍微有那么一点陌生。
  2. 备注说明的 ”nonsuspend" 和 “suspend",这些概念都是之前没有提出或者说明的。因为挂起还没有提到过,代码里面也是用的thread。这里如果写异步,或者其他线程会更加容易理解。
kylooh commented 1 year ago

第7页的图1-4和1-5,这里提到了“同步化”,但是并没有和前面一样的给出代码演示,就显得很莫名其妙,不知道作者在说什么,并没有办法理解图为什么可以这么改。 同时,代码1-9提到的线程异常的情况,实际上是一段很不合理的代码,asyncBitmap里面的异常,以及checkUrl的异常,都是预期的异常,属于逻辑层面需要考虑的情况。但是异步异常并不是一个预期的异常,是系统级别的异常,即使都是直接显示异常信息,但也不应该把这两个视为同一种处理,这俩只是恰好在形式上一致了。如果出于这种这种目的合并线程异常和业务异常,我觉得是完全不合理的,多数响应式框架中,框架本身的异步异常,并不会直接被流内捕获。 我其实给不出很好的建议,唯一能想到的就似乎在thread外面加一个try-catch-throw,转换成一种业务异常。话说我这么多话,会不会让benny讨厌,呜呜呜😭第二遍看,感觉到处都是问题,我都怀疑自己是不是benny铁粉了

第一个点,异步逻辑同步化。

图 1-4 之前有这么一段: “换个角度,异常也是函数调用结果的一种,既然asyncBitmap本身也可能抛出异常,那我们完全可以对抛出的异常与返回的结果一视同仁。如果有一些手段能帮我们把异常的处理合并,我们处理起来就会相对轻松一些。仔细对比图1-4和图1-5,同样存在异步逻辑,只不过后者的异步的调用流程通过编译器或者其他手段简化成了“同步化”的调用,因此前者需要分别处理A到B和C到D处的异常,而后者对整体流程做一次处理即可,复杂度明显降低。”

原文在图 1-5 之后:“异步逻辑同步化正是Kotlin协程要解决的问题。”

这两张图对比的就是异常的异步和同步处理,我们已经知道了异步处理异常比较麻烦,因此自然地想到有没有什么办法能够实现异步同步化。这一节其实还在引出问题,具体的解法会在下一节统一给出,所以就没有给出所谓的异步代码同步化的代码。如果给出来的话,除了影响行文思路,还会面临一个问题,这段代码要不要解释?不解释的话它为什么能异步代码同步化?

第二个点,代码 1-9 的异常问题。 这里其实没有系统异常,都是逻辑异常,checkUrl 和 asyncBitmap 调用的时候可能出现的异常例如 url 不合法,线程池拒绝等;回调里面接收的异常例如解析失败,IO 异常等。这两类异常对于业务上层来讲是一样的,都是业务逻辑的异常,实践当中绝大多数情况下处理的函数都是同一个。

这个地方的关键在于,showError 被调用了两次,除了写起来不简便之外,这还是个代码易错点,你会在实践中看到有的人一开始并没有意识到这两处代码都需要处理异常,后面又意识到了,然后用了不同的代码去做异常处理,结果就是后面两处的逻辑开始不一致,进而引发问题。

我明白了,benny是想引出”怎么样才能让异步调用,看起来同步调用一样”。PS: 我感觉上面这句话,才更像是benny会说出来的🤣感觉benny的下一期视频已经在我脑子里放出来了。

我这里的问题其实其实还是带入到了一个小白一点的角色,如果我是一个小白,我能看懂图1-4和代码1-9是同样的逻辑,但是图1-5就会让我很茫然。但是如果我稍微理解了一点挂起,那我就知道这里其实就是一个async-await。也就是和我前面提到的同样的问题,我了解不够多的情况下,这里的suspend我就没法理解是什么意思。

而且我觉得这里应该拿出benny最擅长的“假如说”来写,会显得更加自然。就像benny回复我的内容:“我们已经知道了异步处理异常比较麻烦,因此自然地想到有没有什么办法能够实现异步同步化。”把这个过程提出来,比直接对比要好。


异常处理可能是我个人风格的问题,对于代码1-9,在我看来等价于下面这个形式

try {
  checkUrl(url)
} catch (e: Exception) {
  showError(e)
}

asyncBitmap(url, onSuccess=::show, onError=::showError)

进一步

checkUrl(url, onError=::showError)
asyncBitmap(url, onSuccess=::show, onError=::showError)

如果外部的 try 同时处理了线程异常,那就算是做了职责之外的事情了。如果说异步异常需要处理,则是在开始异步的地方,使用带 onFail 参数的方式去异步,而不是把异步当成隐式错误,向外传递,这样肯定会有人注意不到。原则大概就是:”要么显式的关心,要么不要关心“

🤔我一开始读的时候,以为这里想说的是,异步会导致有很多需要关心的异常,增加出很多try-catch代码,有没有一种形式可以让大家不在每次异步的时候都去关心,而是统一处理。所以,我开始是觉得这里可能用多个异步代码来体现出,每个地方都要处理很麻烦。甚至说,还有那种带有事务特性的逻辑,一处失败了,需要继续向上返回重试的。

🤣因为比较图1-4和图1-5我就返现了错误处理统一了(PS:图1-4的返回点是不是应该写到Main线程里)其实我怕你觉得我有点来事的,我还是全部看完了再来评论吧,而且我读的也超慢,老是没时间(。_。)

bennyhuo commented 1 year ago

图 1-3 确实有点儿奇怪。我回头检查一下底稿看看。

bennyhuo commented 1 year ago

图 1-4 异常回调的时候是在异步线程的,除非再主动切换主线程,那要求主线程必须支持事件循环。结合前面的 asyncBitmap,最终结束是在异步线程里面的。

bennyhuo commented 1 year ago

加一下微信群吧,有问题直接群里聊。

kylooh commented 1 year ago

加一下微信群吧,有问题直接群里聊。

🤣我好像都在,我毕竟是benny的颜值粉,我是那个冰冰的小饼干,惭愧

bennyhuo commented 1 year ago

加一下微信群吧,有问题直接群里聊。

🤣我好像都在,我毕竟是benny的颜值粉,我是那个冰冰的小饼干,惭愧

原来如此。催更群更活跃一些,如果有兴趣加入,可以加我微信,我拉你进去

kylooh commented 1 year ago

加一下微信群吧,有问题直接群里聊。

rofl我好像都在,我毕竟是benny的颜值粉,我是那个冰冰的小饼干,惭愧

原来如此。催更群更活跃一些,如果有兴趣加入,可以加我微信,我拉你进去

我也在,benny十年老粉,必须在