Kotlin

Kotlin协程

2021-08-19  本文已影响0人  liyihuanx

本片文章讲一些kotlin协程的基础

1.suspend关键字

我们知道suspend函数时协程中的重点,很多人也知道suspend函数,就是挂起和恢复。但是suspend函数为什么能挂起和恢复,怎么挂起和恢复呢??

从java的角度来看

1.我在kotlin中定义了suspend函数和一个普通函数,,且都没有返回值。
2.在反编译之后可以看出,带有suspend关键字的函数,他需要传入一个Continuation对象,且返回类型为object。而普通的函数还是void

image.png image.png

问题一,Continuation是什么?

我们先想一个问题,我们对一个东西做操作的时候,通常会拿到它的信息做出判断才能去做相应操作,那么一个挂起函数的信息是保存在哪里的呢?带着这个问题我们再去看一下Continuation

1.点击去源码中查看,可以知道Continuation就是一个接口,里面有一个context上下文和resumeWith方法。(注: resumeWith等于resume + resumeWithException)

2.通常来说context就是一个数据集合,这也不例外,所以我们的所有信息都可以保存在这且获取

3.从resumeWith的注释我们可以知道,他就是传递一个成功或者失败的方法,那不就等于一个回调吗


image.png

问题二,Continuation从哪来?

说着之前我们再来想一个问题,一个挂起函数为什么只能在另一个挂起函数中调用或者协程中调用呢?

1.在Kotlin中Continuation对象编译器会给我们传入,所以我们再方法中不需要再传了,但是编译器的Continuation对象又从哪里来的??? 让我们再看看上面的问题,是不是明白了什么?

2.补充说明一下,Kotlin中通过系统提供的suspendCoroutine方法可以拿到当前挂起函数的Continuation对象

问题三,为什么kotlin中没返回值,在java中却返回object?

1.我们知道调用一个挂起函数 不等于 这个函数一定会挂起。但如果挂起函数真正挂起的话会返回一个挂起标志COROUTINE_SUSPENDED,所以你并不知道会返回个什么东西。如果不挂起和普通函数没什么区别,也可以返回String等其他类型。
2.补充说明一下,真正的挂起需要有切线程的操作

2.如何创建一个协程

通过查看源码,最后找到以下两个方法
我们可以看出两个方法都传入一个Continuation对象,然后再返回一个新的Continuation对象


image.png

问题一,两个Continuation作用分别是什么

1.传入的Continuation,是给回调用的,为了能够调用resumeWith返回结果,所以取名也是completion

2.返回的Continuation, 个人认为是作为协程的真正载体,也可以认为是启动协程的对象。因为我们可以看到startContinuatine方法中,调用了resume方法。而resume方法又是Continuation中的。所以我认为,一个协程要启动,就要执行resume方法。所以也就返回了一个resume对象

问题二,怎么用原生的方法创建协程

从源码的创建方法可以看出来,创建一个协程,只需要一个suspend函数一个continuation对象一个储存协程信息的上下文CoroutineContext数据集就好了,于是我便这样简单的创建了一下

image.png

3.拦截器

1.协程也有拦截器,通过查找源码我发现,在创建的时候发现一个熟悉的单词。intercepted

image.png

2.经过一番查找,找到了这个方法的实现处ContinuationImpl
3.可以看到源码中在context,也就是CoroutineContext中,也就是一个数据集合中,去查找ContinuationInterceptor拦截器。且最后返回一个Continuation对象

image.png

4.顺着源码进去查看ContinuationInterceptor,发现她就是一个实现了CoroutineContext.Element的类,也就是说可以作为一个元素保存在CoroutineContext上下文中。

image.png

问题一,我们要是自己写一个继承ContinuationInterceptor的类,是不是也能做到拦截的效果?

1.那就来写一个Log拦截器吧!写出来之后发现interceptContinuation 方法要怎么实现啊?但是在返回值中我们可以知道,他是要返回一个Continuation对象的

image.png

2.而源码中也正是靠着这个方法,才能返回Continuation对象的

image.png

3.所以我们再写一个类继承Continuation不就好了吗,于是乎

image.png

4.看起来好像还挺对,我们就试一试验证一下好了。但是要怎么用呢
我们知道拦截器就是CoroutineContext.Element,那么他就可以保存在CoroutineContext中。那么在创建线程的时候先创建一个空的CoroutineContext,然后再添加进去就好了。

image.png

问题二,拦截器是在哪一步帮我们拦截做处理的?

1.其实就是拦截给suspend使用的continuation的resumeWith方法

image.png

2.把这一段代码输出一下的结果,是这样的。可见在输出结果之前都会打印出log拦截器中的log语句。


image.png

问题三,添加多个拦截器会怎样?

经过我的实验得到的是,添加多个拦截器生效的只有一个,因为拦截器的Key是单例的。

4.调度器

调度器就是让我们使用协程的时候,能像使用同步代码一样去使用异步的方法。
其中就是调度器帮我们做了线程切换的处理。

1.那么他是如何做到在异步请求之后完成之后,帮我们切线程的呢?
2.结合上面拦截器的思想,我们大概推测,是不是做一个拦截器,让他在执行suspend闭包返回结果之前,给他切换线程,不就ok了吗?
3.我在原本的Log拦截器中,新建了一个线程去执行这两句代码,然后对比输出结果
4.可以看出,原本在main线程执行的代码,切换到Thread0线程去了

image.png image.png image.png

所以我顺着源码找了下去,找到源码中添加线程调度的地方
1.最终发现,调度器Dispatch也实现了拦截器的接口,并且他也是一个CoroutineContext的元素
2.所以我们可以看到,调度器也可以往传入的context中直接添加

image.png image.png

问题一,调度器在启动和恢复都可以帮我们调度,是在哪调用的?

1.通过我的不断调试,我找到了下面两个方法。可以一眼看出,他在启动和回复时候都做了时候需要调度切换线程的判断。
2.resumeCancellableWith就是在start一个协程的时候调用的。


image.png image.png

最后

1.感觉说的不是很全,有些东西不知道如何表达,还有一些需要补充的东西,会等以后想到了继续补充
2.为了更好的研究协程,接下来会写一个简单的协程框架
3.如果你有什么看法或者建议欢迎提出!

上一篇下一篇

猜你喜欢

热点阅读