Kotlin Coroutine(协程) 基本知识

Kotlin Coroutine(协程)系列:
1. Kotlin Coroutine(协程) 简介
2. Kotlin Coroutine(协程) 基本知识
3. Android中用Kotlin Coroutine(协程)和Retrofit进行网络请求和取消请求
这篇文章主要介绍协程中的一些基本概念。

挂起函数(suspend关键字)

Kotlin中提供了关键字suspend用来描述一个函数为挂起函数,写法如下:

//官方提供的函数
suspend fun delay(timeMillis: Long) {
    ...
}

以上写法就代表delay函数为一个挂起函数。

在前面一篇文章Kotlin Coroutine(协程) 简介中我提到过挂起函数只会挂起当前协程,不会挂起阻塞当前协程所处的线程。事实上,想要执行协程就至少需要一个挂起函数,因此挂起函数是协程中一个非常重要的概念。

特点
  1. 挂起函数能用普通函数的方式获取参数和返回值
  2. 调用挂起函数时,可能会挂起当前协程(如果挂起函数的相关调用已经有结果,那么系统可能会选择不挂起),而不会挂起所在的线程。
  3. 挂起函数执行结束后,协程会自动恢复执行,此时才能继续执行挂起函数后续的代码
  4. 挂起函数只能在协程或其他挂起函数中调用,否则会编译报错
  5. suspend可以将普通函数、扩展函数、lambda表达式均标记为挂起函数

CoroutineScope

官方描述:为协程定义了一个范围

Defines a scope for new coroutines.

也可以理解为协程的上下文环境,更通俗点你可以将其看作为一个协程。

我们再来看下官方源码中的定义:

public interface CoroutineScope {
    /**
     * Context of this scope.
     */
    public val coroutineContext: CoroutineContext
}

通过这个代码我们可以看到CoroutineScope初始定义中只有一个协程上下文CoroutineContext对象,所以协程的上下文对象其实是由CoroutineContext决定的,因此将CoroutineScope看作协程更好理解。

CoroutineContext

协程上下文,包含了协程中的一些元素,主要有JobCoroutineDispatcher

Job

协程的后台任务,它有自己的生命周期,该任务可以被取消。

Job可以有父Job,当父Job被取消时,其所有子Job也会被取消。

Job有三种状态:

  1. isActive 是否处于活动状态
  2. isCompleted 是否完成
  3. isCancelled 是否被取消

可参考下表:

State[isActive][isCompleted][isCancelled]
New (optional initial state)falsefalsefalse
Active (default initial state)truefalsefalse
Completing (transient state)truefalsefalse
Cancelling (transient state)falsefalsetrue
Cancelled (final state)falsetruetrue
Completed (final state)falsetruefalse

当创建协程开始执行并获取到Job对象后,如果想等该协程执行结束再执行其他的业务逻辑,那么可以调用Job.join()方法,该方法会等待该协程任务执行结束,该方法为挂起函数。

Deferred

它是Job的子类,与Job不同的是它可以有返回值,而Job是没有返回值的。

通过调用Deferredawait()方法即可拿到返回值,而await()方法也是一个挂起函数,因此调用该方法时会挂起当前协程,直到拿到返回值协程重新恢复执行。

Android中协程结合Retrofit发起网络请求可以考虑使用该类获取请求结果

CoroutineDispatcher

协程调度器,它可以将协程的执行局限在指定的线程中,它有四个默认的实现:

  1. Dispatchers.Default 默认调度器,在使用launchasync等协程构造器创建协程时,如果不指定调度器则会使用此默认调度器,该调度器会让协程在JVM提供的共享线程池中执行
  2. Dispatchers.Main 主线程调度器,让协程在主线程即UI线程中执行
  3. Dispatchers.IO 让协程在IO线程(子线程)中执行,该调度器会与Dispatchers.Default调度器共享同一个线程池
  4. Dispatchers.Unconfined 该调度器不指定协程在某个线程中执行。设置了该调度器的协程会在调用者线程中启动执行直到第一个挂起点,挂起后,它将在挂起函数执行的线程中恢复,恢复的线程完全取决于该挂起函数在哪个线程执行。
  5. newSingleThreadContext 这是Kotlin另外提供的一个调度器,它会为协程启动一个新的线程。一个专用的线程是一种非常昂贵的资源。 在真实的应用程序中两者都必须被释放,当不再需要的时候,使用 close 函数,或存储在一个顶级变量中使它在整个应用程序中被重用。

另外需要注意的是:协程调度器默认承袭外部协程的调度器。

GlobalScope

这是一个全局的CoroutineScope不会受任何Job约束,通过它创建的是全局协程,它会在整个应用的生命周期中运行,不能被取消

launch函数

这是一个扩展的CoroutineScope实例方法,同时也是一个很常用的协程构建器。

通过其默认参数会创建一个不会阻塞当前线程且会立即执行的协程,该方法会返回一个Job对象,该方法默认承袭所在的CoroutineScope对象的调度器。

val scope = CoroutineScope(Dispatchers.Main + Job())
scrope.launch {
    //协程实现
}

上述代码通过launch创建的协程会在UI线程中执行

val scope = CoroutineScope(Dispatchers.Main + Job())
scrope.launch(Dispatchers.IO) {
    //协程实现
}

上述代码通过launch创建的协程会在IO线程中执行

runBlocking

这是一个全局的协程构建器,可以在任何地方调用。

该构建器会创建一个阻塞当前线程的协程,所以该构建器不建议使用在协程内。

async

launch函数一样,也是CoroutineScope的扩展实例方法,它也是一个常用的协程构建器,不同是它创建协程时返回的是Deferred,通过Deferred可以拿到执行结果

val a = async {
    log("I'm computing a piece of the answer")
    6
}
val b = async {
    log("I'm computing another piece of the answer")
    7
}
log("The answer is ${a.await() * b.await()}")

delay

全局函数

  1. 让协程休眠指定时间,类似于Java中的Thread.sleep的作用
  2. delay是一个挂起函数,调用后不会阻塞挂起当前线程
  3. 当协程的休眠时间到了之后,当前所处协程会重新恢复执行

withContext

切换协程上下文,一般主要用来切换协程所在的线程环境,如从主线程切换到IO线程。

调用该方法不会创建新的协程,同时是一个挂起函数

该方法会有一个返回值,其返回值为withContext中lambda表达式的返回值


下面是我的个人公众号,欢迎关注交流


版权声明:本文为huyongl1989原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。