Kotlin协程笔记

241 阅读5分钟

参考自:史上最详Android版kotlin协程入门进阶实战

1.什么是协程

从三个比较流行的说法来解释:
1.协程是轻量级线程(官方表述)
换个说法,协程就是方法调用封装成类线程的api.方法调用当然比线程切换轻量,而封装成类线程的api后,它形似线程(可手动启动,有各种运行状态,能够协助工作,能够并发执行).因此从这个角度说:它是轻量级线程没错.
当然协程绝不仅仅是方法调用,因为发放调用不能在一个方法执行到一半时挂起,之后又在原点恢复.这一点可以使用EventLoop之类的方式实现.想象一下在库级别将回调风格或Promise/future风格的异步代码封装成同步风格,封装的结果就非常接近协程了.
而协程和线程之间的区别,往大了说,就是普通函数与线程的区别,往小了说,就是EventLoop和线程的区别.它们之间唯一的关系,仅仅在于协程的代码是运行在线程中.
2.线程运行在内核态,协程运行在用户态
主要明白什么是用户态,我们写的几乎所有代码都执行在用户态,协程对于操作系统来说仅仅是第三方提供的库而已,当然运行在用户态.而线程是操作系统级别的东西,运行在内核态.
3.协程是一个线程框架(抛物线表述)
对某些语言,比如Kotlin,这样说是没问题的,Kotlin的协程库可以指定协程运行的线程池,我们只需要操作协程,必要的线程切换操作交给库,从这个角度说,协程就是一个线程框架.
但理论上我们可以在单线程语言如JavaScript,Python上实现协程(事实上他们已经实现了协程),这时我们在叫它线程框架就不合适了.

2.协程的作用

1.以同步的方式写异步代码.
2.减少地狱回调.

3.协程的启动

协程需要运行在协程上下文环境,在非协程环境中启动协程,有三种方式:
1.runBlocking{}:启动一个新协程,并堵塞当前线程.直到内部所有逻辑及子协程逻辑全部执行完成.常在main方法和测试中使用.
2.GlobalScope.launch{}:在应用范围内启动一个新协程,协程的生命周期与应用程序一致,这样启动的协程并不能使线程保活,就像守护线程.由于这样的协程存在启动协程的组件已销毁但协程还存在的情况,极限情况下可能导致资源耗尽,因此并不推荐这样使用.
3.实现CoroutineScope+launch{}:这是在应用中最推荐使用的协程方式-为自己的组件实现CoroutineScope接口,在需要的地方使用launch{}方法启动协程.使得协程和该组件生命周期绑定,组件销毁时,协程一并销毁.从而实现安全可靠的携程调用.

4.协程上下文

协程上下文表示协程的运行环境,包括协程调度器,代表协程本身的job,协程名称,协程ID等.通过CoroutineContext定义,CoroutineContext被定义为一个带索引的集合,集合的元素为Element,上面所提到的调度器,job等都实现了Element接口.
由于CoroutineContext被定义为集合,因此在使用时可以自由组合加减各种上下文元素.

5.协程调度器

调度器是协程上下文中众多元素最重要的一个,通过CoroutineDispatcher定义,它控制了协程以何种策略分配到哪些线程上运行.这里介绍几种常见的调度器.
1.Dispatcher.Default:默认调度器,它使用JVM的共享线程池,该调度器的最大并发数是cpu核心数,默认为2.
2.Dispatcher.Unconfined
非受限调度器,它不会将操作限制在任何线程上执行---在发起协程的线程上执行第一个挂起点之前的操作,在挂起点恢复后由对应的挂起函数决定接下来的操作在哪个线程上执行.
3.Dispatcher.IO
IO调度器,它将一个阻塞的IO任务分流到一个共享的线程池中,使得不阻塞当前线程.该线程池大小为环境变量kotlinx.coroutines.io.parallelism的值,默认是64或核心数的最大者.
该调度器和Dispatchers.Default共享线程,因此使用withContext(Dispatchers.IO)创建新的协程不一定会导致线程的切换.
4.Dispatcher.Main
该调度器限制所有执行都在UI主线程,它是专门用于UI的,并且会随着平台的不同而不同,

6.协程作用域

协程作用域-CoroutineScope,用于管理协程,管理的内容有
1.协程的启动方式-它定义了launch,async,withContext等协程启动方式(以extention的方式),并在这些方法内定义了启动子协时上下文的继承方式.
2.管理协程生命周期-它定义了cancel()的方法,用于取消当前作用域,同时取消作用域内所有协程.
注意:区分作用域和上下文,从定义上看,CoroutineScope和CoroutineContext非常类似,最终目的都是协程上下文,但正如Kotlin协程负责人Roman Elizarov在Coroutine Context and Scope中所说,二者的区别只在于使用的目的不同,作用域用于管理协程,而上下只是一个记录协程运行环境的集合.