Day6:三大要素

好的好的,经过了前几篇文章之後,想必大家应该对 Coroutine 有一些了解了吧,我在这边快速复习一下。

Coroutine 是用来解决非同步程序的执行问题,使用它,你就可以避免使用 Callback,也就不会发生 回调地狱(Callback hell)、控制权转移(Inversion of control)的情况。它在官网是以轻量的执行绪介绍。同时建立相同数量的 Coroutine 与执行绪,就可以立刻发现两者的执行速度差异。

接下来要提到的是,它是属於无栈协程,也就是说,在 Coroutine 内部不像执行绪一样使用调用栈 (call stack) 用来储存 Coroutine 的上下文,在 Coroutine 的内部是使用 Callback 的机制在进行非同步处理,只不过 Coroutine 把这层包起来了。所以我们感觉不出来是用 Callback 机制。

另外, 在 Coroutine 中,我们将耗时、延时的任务定义在 suspend 函式中,而这个 suspend 函式必须要在 Coroutine scope 中执行,无法在 Coroutine scope 以外呼叫这个 suspend 函式,原因无他,就是因为在 suspend 函式里面有包含了 Continuation 的实例,而这个实例包含着 Coroutine 上下文,当完成 suspend 函式之後,就可以调用 Continuation 中的 resumeWith 来把 Coroutine 切换回来。这就是我说的 Callback 机制。

要如何建立一个 Coroutine 呢?

根据前面的描述,Kotlin 的 Coroutine 必须要在一个称作 Coroutine Scope 的范围内才能运作。除了需要一个 Coroutine Scope 以外,在这个范围内,如果需要执行一个非同步的程序, Kotlin 提供了一个关键字 suspend 用来定义这个函式是一个需要暂停的函式。最後,我们知道 Coroutine 其实也是运行在执行绪中,那麽我们也可以依照我们的需求切换执行绪,在 Kotlin 的 Coroutine 是使用调度器 (Dispatchers) 来切换不同的执行绪。

三个重要要素:

3 elements

  1. CoroutineScope:Coroutine 只能够在一范围内才能够被执行。
  2. Suspend function:用来定义非同步函式,调用该函式时,协程便会暂停,直到程序完成其工作时,才会在暂停的地方恢复。
  3. Dispatchers:根据不同的 Dispatchers,Coroutine 会选择适当的执行绪来执行相对应的工作。

CoroutineScope

有三种建立 CoroutineScope 的方式

  1. coroutineScope():建立一个 Coroutine Scope,但是不建立新的 Coroutine。也就是说它内部的 coroutineContext 是由继承外部的 CoroutineScope 得来的。目的是用来分解并行工作的。
  2. launch():建立一个新的 Coroutine,会回传一个 Job ,我们可以在 launch() 中直接执行任务,或是透过回传的 Job 在适当的时机呼叫其 start() 或者 join() 延迟执行。适合使用在没有回传值的非同步任务。
  3. async():如果非同步任务是有回传值的,那就需要使用 async() 来建立 Coroutine Scope, async() 的回传值将会回传一个 Deferred<T> ,当任务完成时,我们可以使用 await() 来取得回传值得内容。

Suspend function

Kotlin 的 coroutine 使用 suspend 关键字用来把函式定义成可暂停的 (suspendable)。

suspend fun suspendFun() = coroutineScope {
		doSomething()
}

如上,在 suspendFun() 前方加上 suspend 就会让这个函式变成可暂停的函式,也就可以在 Coroutine scope 中使用,并在调用时暂停,完成任务时在原地恢复。

在 suspend 函式中,我们除了可以调用其他的 suspend 函式,当然也可以调用普通的函式。

Coroutine 定义了几个 top-level 的 suspend function 供我们使用,如下:

  • delay() :暂停 Coroutine ,与执行绪的 Thread.sleep() 不同,不会停止执行绪。
  • yield() :把目前 Coroutine 调度器执行的任务暂停并让给其他的任务来执行。
  • withContext() :在当下的 Coroutine scope 中,使用不同的 context。
  • withTimeout() :设定执行时间,如果时间到还没有完成,就会抛出例外。
  • withTimeoutOrNull() :同上,只不过是回传一个 Null 值。
  • awaitAll() :等待所有的 Deferred 完成,或是其中一个发生错误。
  • joinAll() :暂停 Coroutine 直到所有的 Job 都完成。等同於 jobs.forEach { it.join() }.

Dispatchers

调度器是用来决定该 Coroutine 要在哪个执行绪来执行。如果没有设定,将会使用 Dispatchers.Default。

有四种 Dispatchers,如下:

  • Dispatchers.Default
  • Dispatchers.Main
  • Dispatchers.IO:将阻塞 IO 任务在共享执行绪池执行。
  • Dispatchers.Unconfined

简单来说:Dispatchers.Default 是用在背景运算的 coroutine ,而 Dispatchers.Main 是用在画面更新上(主执行绪)。

小结

Coroutine 是由三个要素所组成,CoroutineScope、suspend fun 以及 Dispatchers。suspend 函式 只能在 CoroutineScope 中执行,根据函式是否有回传值,我们可以选择使用 launch() 或是 async() 来建立一个 coroutineScope,前者是没有回传值的,後者则是有一个回传值的。

除了我们自己定义的 suspend 函式以外,Coroutine 也有定义了一些 top-level suspend 函式。如 delay()

最後,每一个 CoroutineScope 都可以带入 CoroutineContext,如果没有带入,那麽就会使用预设的值,预设的 CoroutineContext 是 Dispatchers.Default。

心智图

心智图

特别感谢

Kotlin Taiwan User Group

Kotlin 读书会


<<:  Day11 网页排版好朋友 - Flexbox

>>:  WordPress 强制使用 https 连线 (使用 SSL 凭证)

[Day13] - 於 Django 中进行资料库设定

建立完环境之後,我们需要在Django的设定中,也告诉Django 资料库的资讯,Django 才会...

Day2 基础安装 + 加码:nvm

今天正式进入主题~ 在开始前我们必须先把环境建立起来,我知道很多人会使用webpack,但这边我是使...

【Side Project】 顾客点菜单画面设计2-Bootstrap

最近有位小夥伴在工作上遇到了一些问题。 小夥伴问: 我在一家公司里工作了九年了。公司最近招了一个新的...

[Day30]程序菜鸟自学C++资料结构演算法 – 心得总结

前言:今天是铁人赛的第30天,但实际上花了一个多月的时间准备,因为自认自己的实力还不够好,所以有提前...

30天轻松学会unity自制游戏-制作子弹

做完Player後先帮来上个简易武器吧,找到Player Bullet 0一样拖曳到场景,看是否需要...