# Kotlin协程从入门到入土 ## 0x00 什么是协程 协程,英文Coroutines,是一种比线程更加轻量级的存在。 协程不是进程,也不是线程,它就是一个可以在某个地方挂起的特殊函数,并且可以重新在挂起处继续运行。所以说,协程与进程、线程相比,不是一个维度的概念。 协程已经在Python、JS等许多编程语言上有所实现,一般情况下,协程被认为是比线程更轻量级的异步事件处理方式,所谓协程,是在编程语言层面上对同时执行多个异步任务上的实现。 > 协程通过将复杂性放入库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解决其异步性。该库可以将用户代码的相关部分包装为回调、订阅相关事件、在不同线程(甚至不同机器!)上调度执行,而代码则保持如同顺序执行一样简单。 > > 协程是一种并发设计模式,您可以在Android平台上使用它来简化异步执行的代码 简单来说,协程可以帮助开发者以同步方式编写异步代码,解决以往编写异步程序时出现的“回调地狱”的问题。 ## 0x11 Kotlin上协程的分类 ### 创建最简单的协程 先上代码: ```Kotlin // 调用阻塞式协程 runBlocking { Log.d("runBlocking", "启动一个协程") } // 调用launch启动协程 GlobalScope.launch { Log.d("launch", "启动一个协程") } // 调用async启动协程 GlobalScope.async { Log.d("async", "启动一个协程") } ``` 如上面代码所示,Kotlin中创建启动协程主要有3种方式: 1. runBlocking - 启动一个阻塞式协程,协程体代码块内代码会阻塞所在线程,返回值为泛型T,就是在协程体内最后一行返回的数据类型 2. launch - 启动一个非阻塞式协程,必须在协程作用域(CoroutineScope)内启动,但不会阻塞所在线程,返回值为Job 3. async - 启动一个非阻塞式协程,必须在协程作用域(CoroutineScope)内启动,但不会阻塞所在线程,返回值为一个Deferred,T泛型为协程体内最后一行返回的数据类型。 - 协程的返回类型 1. runBlocking的泛型返回值T runBlocking在默认情况下类似于一个封装好的方法,当然可以通过指定该方法运行的线程,让这个方法不阻塞主线程,这样就类似于我们在Java中的Thread,并且将run方法执行的结果最终返回回来,这里不做过多讨论。 2. launch的返回类型Job Job我们可以认为他就是一个协程作业是通过CoroutineScope.launch生成的,同时它运行一个指定的代码块,并在该代码块完成时完成。我们可以通过isActive、isCompleted、isCancelled来获取到Job的当前状态 我们可以通过Job获取当前协程执行的状态,如下表所示,协程也存在对应的生命周期。 | State | [isActive] | [isCompleted] | [isCancelled] | | ----- | :--------: | :-----------: | :----------: | | New (optional initial state) | false | false | false | | Active (default initial state)| true | false | false | | Completing (transient state) | true | false | false | | Cancelling (transient state) | false| false | true | | Cancelled (final state) | false | true | true | | Completed (final state) | false |true| false | 3. async的返回类型Deferred Deferred是继承自Job的,我们可以通过Deferred.await()方法获取协程的泛型T返回值。 ## 0x12 详解协程参数 上面的三种创建启动协程的方法,其构造方法内的参数都很类似。 ```Kotlin public fun runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job public fun CoroutineScope.async( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T ): ``` 可以看到,runBlocking相比其他两个方法,缺少了协程启动模式CoroutineStart类型的参数,三个方法参数都有协程下上文CoroutineContext和协程执行体CoroutineScope.()。接下来我们就主要了解这三个参数类型。 - 协程上下文CoroutineContext 它是一个包含了用户定义的一些各种不同元素的Element对象集合。其中主要元素是Job、协程调度器CoroutineDispatcher、还有包含协程异常CoroutineExceptionHandler、拦截器ContinuationInterceptor、协程名CoroutineName等。这些数据都是和协程密切相关的,每一个Element都一个唯一key。 通过协程上下文,我们可以定义协程调度器、协程名称、协程的异常处理方式等等,而这些都可以通过协程上下文中的重载关键字来实现。 - 协程调度器CoroutineDispatcher 如上文所述,协程调度器也实现了协程上下文的接口,协程调度器可以指定当前协程所在的线程。 Kotlin默认提供了4中类型的协程调度线程: - Default:默认调度器,CPU密集型任务调度器,适合处理后台计算。通常处理一些单纯的计算任务,或者执行时间较短任务。比如:Json的解析,数据计算等。 - IO:IO调度器,,IO密集型任务调度器,适合执行IO相关操作。比如:网络处理,数据库操作,文件操作等。 - Main:UI调度器, 即在主线程上执行,通常用于UI交互,刷新等。 - Unconfined:非受限调度器,又或者称为“无所谓”调度器,不要求协程执行在特定线程上。 不指定调度器时,协程运行在Default默认调度器下,需要注意的是,Default调度器对应的线程并非为主线程。 - 协程启动模式 CoroutineStart协程启动模式,是启动协程时需要传入的第二个参数。协程启动有4种 - DEFAULT 默认启动模式,我们可以称之为饿汉启动模式,因为协程创建后立即开始调度,虽然是立即调度,单不是立即执行,有可能在执行前被取消。 - LAZY 懒汉启动模式,启动后并不会有任何调度行为,直到我们需要它执行的时候才会产生调度。也就是说只有我们主动的调用Job的start、join或者await等函数时才会开始调度。 - ATOMIC 一样也是在协程创建后立即开始调度,但是它和DEFAULT模式有一点不一样,通过ATOMIC模式启动的协程执行到第一个挂起点之前是不响应cancel 取消操作的,ATOMIC一定要涉及到协程挂起后cancel 取消操作的时候才有意义。 - UNDISPATCHED 协程在这种模式下会直接开始在当前线程下执行,直到运行到第一个挂起点。这听起来有点像 ATOMIC,不同之处在于UNDISPATCHED是不经过任何调度器就开始执行的。当然遇到挂起点之后的执行,将取决于挂起点本身的逻辑和协程上下文中的调度器。 这里需要特别注意的是UNDISPATCHED,非调度启动模式,在这个模式下,协程会在父协程所在线程直接执行,直到挂起函数出现,如果指定了对应的调度器,则会切换到对应的线程执行。 -