Kotlin Coroutines 完全解析(一),协程简介

本文基于 Kotlin v1.3.0-rc-146,Kotlin-Coroutines v1.0.0-RC1

Kotlin 中引入 Coroutine(协程) 的概念,可以帮助编写异步代码,目前还是试验性的。国内详细介绍协程的资料比较少,所以我打算写 Kotlin Coroutines(协程) 完全解析的系列文章,希望可以帮助大家更好地理解协程。这是系列文章的第一篇,简单介绍协程的特点和一些基本概念。协程主要的目的是简化异步编程,那么先从为什么需要协程来编写异步代码开始。

Kotlin Coroutine 终于正式发布了,所以我跟进最新的正式版更新了本文相关内容

1. 为什么需要协程?

异步编程中最为常见的场景是:在后台线程执行一个复杂任务,下一个任务依赖于上一个任务的执行结果,所以必须等待上一个任务执行完成后才能开始执行。看下面代码中的三个函数,后两个函数都依赖于前一个函数的执行结果。

三个函数中的操作都是耗时操作,因此不能直接在 UI 线程中运行,而且后两个函数都依赖于前一个函数的执行结果,三个任务不能并行运行,该如何解决这个问题呢?

1.1 回调

常见的做法是使用回调,把之后需要执行的任务封装为回调。

回调在只有两个任务的场景是非常简单实用的,很多网络请求框架的 onSuccess Listener 就是使用回调,但是在三个以上任务的场景中就会出现多层回调嵌套的问题,而且不方便处理异常。

1.2 Future

Java 8 引入的 CompletableFuture 可以将多个任务串联起来,可以避免多层嵌套的问题。

上面代码中使用连接符串联起三个任务,最后的方法还可以统一处理异常情况,但是只能在 Java 8 以上才能使用。

1.3 Rx 编程

CompletableFuture 的方式有点类似 Rx 系列的链式调用,这也是目前大多数推荐的做法。

RxJava 丰富的操作符、简便的线程调度、异常处理使得大多数数满意,我也如此,但是还没有更简洁易读的写法呢?

1.4 协程

下面是使用 Kotlin 协程的代码:

使用协程后的代码非常简洁,以顺序的方式书写异步代码,不会阻塞当前 UI 线程,错误处理也和平常代码一样简单。

2. 协程是什么

2.1 Gradle 引入

2.2 协程的定义

先看官方文档的描述:

协程通过将复杂性放入库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解决其异步性。该库可以将用户代码的相关部分包装为回调、订阅相关事件、在不同线程(甚至不同机器)上调度执行,而代码则保持如同顺序执行一样简单。

协程的开发人员 Roman Elizarov 是这样描述协程的:协程就像非常轻量级的线程。线程是由系统调度的,线程切换或线程阻塞的开销都比较大。而协程依赖于线程,但是协程挂起时不需要阻塞线程,几乎是无代价的,协程是由开发者控制的。所以协程也像用户态的线程,非常轻量级,一个线程中可以创建任意个协程。

总而言之:协程可以简化异步编程,可以顺序地表达程序,协程也提供了一种避免阻塞线程并用更廉价、更可控的操作替代线程阻塞的方法 -- 协程挂起。

3. 协程的基本概念

下面通过上面协程的例子来介绍协程中的一些基本概念:

3.1 挂起函数

和函数前面有修饰符标记,这表示两个函数都是挂起函数。挂起函数能够以与普通函数相同的方式获取参数和返回值,但是调用函数可能挂起协程(如果相关调用的结果已经可用,库可以决定继续进行而不挂起),挂起函数挂起协程时,不会阻塞协程所在的线程。挂起函数执行完成后会恢复协程,后面的代码才会继续执行。但是挂起函数只能在协程中或其他挂起函数中调用。事实上,要启动协程,至少要有一个挂起函数,它通常是一个挂起 lambda 表达式。所以修饰符可以标记普通函数、扩展函数和 lambda 表达式。

挂起函数只能在协程中或其他挂起函数中调用,上面例子中函数就创建了一个协程。

函数:

从上面函数定义中可以看到协程的一些重要的概念:CoroutineContext、CoroutineDispatcher、Job,下面来一一介绍这些概念。

3.1 CoroutineScope 和 CoroutineContext

CoroutineScope,可以理解为协程本身,包含了 CoroutineContext。

CoroutineContext,协程上下文,是一些元素的集合,主要包括 Job 和 CoroutineDispatcher 元素,可以代表一个协程的场景。

EmptyCoroutineContext 表示一个空的协程上下文。

3.2 CoroutineDispatcher

CoroutineDispatcher,协程调度器,决定协程所在的线程或线程池。它可以指定协程运行于特定的一个线程、一个线程池或者不指定任何线程(这样协程就会运行于当前线程)。中 CoroutineDispatcher 有三种标准实现、,和,Unconfined 就是不指定线程。

函数定义如果不指定或者没有其他的,默认的协程调度器就是,是一个协程调度器,其指定的线程为共有的线程池,线程数量至少为 2 最大与 CPU 数相同。

3.3 Job & Deferred

Job,任务,封装了协程中需要执行的代码逻辑。Job 可以取消并且有简单生命周期,它有三种状态:

Job 完成时是没有返回值的,如果需要返回值的话,应该使用 Deferred,它是 Job 的子类。

3.4 Coroutine builders

函数属于协程构建器 Coroutine builders,Kotlin 中还有其他几种 Builders,负责创建协程。

3.4.1 CoroutineScope.launch {}

是最常用的 Coroutine builders,不阻塞当前线程,在后台创建一个新协程,也可以指定协程调度器,例如在 Android 中常用的。

3.4.2 runBlocking {}

是创建一个新的协程同时阻塞当前线程,直到协程结束。这个不应该在协程中使用,主要是为函数和测试设计的。

3.4.3 withContext {}

不会创建新的协程,在指定协程上运行挂起代码块,并挂起该协程直至代码块运行完成。

3.4.4 async {}

可以实现与 launch builder 一样的效果,在后台创建一个新协程,唯一的区别是它有返回值,因为返回的是 Deferred 类型。

获取的返回值需要通过函数,它也是是个挂起函数,调用时会挂起当前协程直到 async 中代码执行完并返回某个值。

4. 小结

Kotlin 协程可以极大地简化异步编程,虽然刚开始接触的时候学习比较吃力,但是接触过一段时间相信绝对会爱上它。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181123A0I91L00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

同媒体快讯

扫码关注腾讯云开发者

领取腾讯云代金券