Day28:复习 Coroutine

不知不觉来到了第28天,最後我们来做个复习吧。

Coroutine 的目的

用来解决非同步程序执行的问题,在以前面对非同步的程序时,我们可能需要建立一个新的执行绪,在这个执行绪上执行完耗时任务之後,接者再把结果由此执行绪传回至原本的执行绪。

因为非同步程序的特性,我们需要使用 Callback 在耗时任务结束後把结果回传回来,但是如果有好几个 Callback 嵌套,也就是一个结果会传至另一个函式作为参数,这样就会产生所谓的 Callback hell(回调地狱),增加了维护的难度。

Coroutine 的目的就是要让非同步的程序执行就像是同步的程序执行一样,也就是说不需要 Callback 来接收不同执行绪的结果。


四大特性

  1. 编写非同步程序与同步程序一样容易。
    • Callback hell 再会了。
    • 从哪里暂停,就从哪里恢复。
  2. Coroutines 可看作是轻量级的执行绪。
    • Coroutine 是在执行绪底下,每一个执行绪中含有很多个 Coroutine。
  3. 可以轻易地在执行绪中切换。
    • 可以使用 Dispatchers 使用不同的执行绪。
    • Coroutine 定义了几种 Dispatchers ,我们只需要选择适当的 Dispatchers,然後 Coroutine 就会帮我们选择适当的执行绪。
  4. 轻松取消 Coroutine 的执行。
    • 若要结束执行绪,则必须让执行绪里面的任务执行完毕之後,才可以结束,否则有可能会造成 memory leak。
    • 但是,Coroutine 能够轻松被取消,每一个 Coroutine 都会回传一个 Job,其中包含了 cancel() 只要呼叫 cancel() 该 Coroutine 的任务就会被取消。
    • 因为 Coroutine 是协作式的多工,执行中的程序会定期地放弃自己的执行权利,那麽当我们呼叫 cancel() 的时候,我们就可以自行将任务给取消。

三大要素

Suspend 函式

我们将耗时任务写在 suspend 函式中,当程序执行到 suspend 函式时,将会暂停该 coroutine ,并且切换自动切换至其他的 coroutine,等到 suspend 函式完成它的任务时,就会在原本暂停的点继续开始下面的任务。

suspend 函式只能在 coroutine scope 或是另外的 suspend 函式内呼叫。

CoroutineScope (范围)

要执行 suspend 函式,必须要在 CoroutineScope 中执行,而建立 Scope 的方式有两种,一种是 launch ,另一种则是 async 。其中 launch 是没有回传值的(Side-effect),而 async 是有回传值的。launch 回传的是 Job,而 async 回传的是 Deferred,其中 Deferred 也是继承 Job 的类别。如前面所说,我们可以直接呼叫 Job 的 cancel() 取消 coroutine 的任务。

Dispatchers (调度器)

建立执行绪是需要较多资源,如果每次需要耗时任务都建立一个执行序,那麽是不太切实际的,所以Coroutine 中,使用者是不能直接使用执行绪的,这边提供了几种不同的 Dispatchers,使用者根据需求选择不同的 Dispatchers,Coroutine 就会选择适当的执行绪/执行绪池。这些执行绪/执行绪池是不会被关闭的,所以任务可以很快速的切换至不同的执行绪中。另外,如果要在 Coroutine 中切换不同的调度器,我们可以使用 withContext


结构化并发

我们可以在一个 Coroutine Scope 中执行多个 Job,它们彼此之间的关系是父-子的关系,也就是说,当 Coroutine Scope 里面的所有 Job 结束之後,外层(父层)的 Coroutine 才能够结束。如果其中一个子 Coroutine 发生错误被取消,那麽後面还没有完成的任务也会一并被取消。另外,如果父 coroutine 被取消,那麽所有在里面的子 Coroutine 也会一并被取消。这样子的好处就是不会有父类别已经被取消,但是子 coroutine 却还在执行,这样子就有可能会发生 memory leak 的情况。

SupervisorJob()

如果在一个 CoroutineScope 中,其中一个子 coroutine 被取消,如果我们使用的是 Job(),那麽後面的所有 job 都会被取消。但是如果使用的是 SupervisorJob(),当其中一个 Job 发生错误被取消时,後面的任务还是会继续执行直到完成。


例外处理

在 coroutine 中,我们一样可以使用 try-catch 来将可能会发生例外(Exception) 的程序码包起来,当发生例外的时候,就会被 try-catch 给拦截下来。如果我们希望能够在 CoroutineScope 的范围内能够捕捉例外呢?我们可以使用 CoroutineExceptionHandler。不过 async 的例外会在呼叫 await() 时才会发生,所以如果要捕捉 async 的例外,则还是必须要在 await() 上使用 try-catch 。


内建的 suspend 函式

  • delay(): 将目前的 coroutine 暂停 n ms。
  • yield():将目前 coroutine 的执行绪使用权让给下一个。
  • withContext:在目前的 coroutine 中使用另外的 CoroutineContext。
  • withTimeout:使用 withTimeout 在耗时任务上,当任务的执行时间超过预期,则会报出TimeoutCancellationException。
  • withTimeoutOrNull:与 withTimeout 类似,不过时间到的时候是会回传 null 而不是报出 TimeoutCancellationException。
  • joinAll():如果有多个 Job 存在 List 中,我们可以使用 joinAll() 让所有的 Job 都呼叫其 join(),其中呼叫 join() 是会让该 coroutine 的任务提前先做。
  • awaitAll():如果有多个 Deferred 存在 List 中,我们可以使用 awaitAll() 来将所有的任务都被呼叫 await() ,所以 awaitAll() 得到结果就是所有的 async 都完成的时候。
  • cancelAndJoin():如果 job 里面的任务太久,我们想要取消它,我们会使用 cancel(),当取消之後,我们可以在用 join() 执行该任务後面的动作。在 Coroutine 中,提供了一个整合的函式, cancelAndJoin() 让我们可以一次呼叫两个函式。

呼,前十四天的内容就在这篇做个复习了,如果有什麽遗漏,或是有错误,麻烦请跟我说,下一篇将复习後面的部分: Channel、Flow、SharedFlow、StateFlow。

特别感谢

Kotlin Taiwan User Group

Kotlin 读书会


<<:  DAY 21 Big Data 5Vs – Variety(速度) Kinesis (1)

>>:  Day17:关於 WS 的使用

将自己的强项点好点满

以工程师职涯发展上有不少的论点跟方向,但大多来说似乎一定要走上管理职在履历上才有所突破。 在写这篇文...

二十八日目:JavaScript XMLHttpRequest 壱ノ章

おっはー(U 'ᴗ' U)⑅ 我是SONYKO 。 今天天气好好。连假我还真tm的都在家写文章呢  ...

Day7 我想知道它哪里比我好很多 在你心中它和我有什麽不同

JavaScript feature 随着越来越深入JavaScript,现在所考察和学习到的co...

#26 JS: HTML DOM Events - Part 4(Start Over Version)

After understanding the basic HTML DOM Event conce...

存取方法

终於度过前面枯燥乏味的内容了...(但它们都很重要,也与今天的主题有关) 今天要来进入重点项目 我们...