Kotlin | 从线程到协程,你是否还存在理解上的疑问
引言
在2022的今天,对于一个 Android
开发同学,如果你使用 Kotlin
作为主要开发语言,那么协程是必不可缺的 异步框架 。不过对于初学者来说,有时候依然存在一些理解问题或者使用上的不解。毕竟我们用了那么多年的回调与线程,突然转变思想,的确需要过程。
本文将结合实际中其他同学遇到的问题来讲讲,从线程到协程,初学者对于 `[同步]` 的理解疑问。
背景
事情源自这样,今天早上在群里,发现有同学问到了这样一个问题:
协程A:开启一个等待页面,
wait
,等到B完成后显示成功 协程B:与下位机通讯,等到下位机回复成功后,通知A协程notify
具体对话图示如下:
这个同学的想法是:
开启两个协程,协程A开启一个等待页面,然后在这里 wait
等待;等协程B这边执行成功后,再通知协程A去刷新。
作为过来人,我们不难第一反应,协程默认不就是同步吗,直接 suspend
就完了啊?为什么要通知呢?不是很麻烦吗?
解决这个问题很简单,但我的第一反应是,他似乎理解错了协程中的同步?但反过来又仔细一想,这个同学为什么能存在疑问,似乎我也曾问过,为什么不可以等待另一个job来通知我完成了呢?所以我更想告诉他为什么要这样写?
对于初使用协程而言,我们的想法应该怎样转变,这也即本文的主章:
面对协程,我们应该怎样去接受解决思路的转变
解决方法
在阐述 [莫须有] 的思想之前,我先写出下面的不同解法,以便大家更好的体会差异:
1. 线程写法
定义两个线程,线程A开始,然后 wait
等待,线程B执行逻辑,成功后,调用线程A notify
.
fun threadTest() {
// job A
val jobA = thread(start = false) {
println("开始执行成功逻辑")
}
jobA.wait()
// job B
thread {
Thread.sleep(1000)
jobA.notify()
}
}
2. 接口回调
如果用 回调 去做,免除 阻塞线程 ,又是这样的写法:
定义一个接口,任务A开始执行,在这里等,等另一边任务B完成后,再调用任务A接口方法即可完成唤醒。
代码语言:javascript复制fun callBackTest() {
val jobA = {
println("开始执行成功逻辑")
}
// jobB开始
thread {
Thread.sleep(1000)
jobA.invoke()
}
}
3. 协程
解析
在 Android
官网中,对协程的描述如下:
协程是一种并发设计模式,您可以在
Android
平台上使用它来简化异步执行
的代码。
说简单点就是,在协程的世界中,一切都是同步,按顺序进行。即一步接一步,我们等待上一步的结果,然后决定是否继续执行下一步。
综合对比上述的解法来看:
- 线程写法:我们需要调用
await
,这将使得正在运行的线程[阻塞],对我们的性能造成了影响; - 回调写法:我们不再阻塞线程,但我们逻辑更复杂化,如果存在多个回调,这将提高阅读成本;
- 协程写法:我们提供了两种不同的写法,即是否需要改善相应方法中的回调。
- 前者在执行任务B时,我们切换到了
IO协程
,并最终将状态返回,接下来,我们判断,如果获得的state是我们想要的写法,就继续操作; - 后者在执行任务B时,利用了
suspendCoroutine
函数,我们可以将一些回调的代码借此改为协程的同步写法,从而获得与前者一致的体验;
- 前者在执行任务B时,我们切换到了
所以协程具有如下的基本特点:
更轻量、 简化异步代码
而面对难解决的异步代码时,我们首要的不应该考虑如何去通知,而是看看能不能将任务拆分,比如将原有需要通知的这一步拆为三步走:
在非协程的世界,我们可能想,先执行任务A,等待任务B成功后,再去通知A继续执行。
而在协程的世界,我们就可以改为:先执行任务A前奏,再去执行任务B,根据任务B的结果决定是否继续执行任务A的后步骤。
扩展
下面这些函数,对于初学者可能会比较有帮助。
suspend
将一段普通代码转换为挂起函数
代码语言:javascript复制suspend {
delay(1000)
withContext(Dispatchers.IO){
}
}
将回调代码转为协程
suspendCoroutine
代码语言:javascript复制 // 可取消的挂起函数
suspendCoroutine<Int> {
// 便于将一些回调操作改善为同步写法
// 成功
it.resumeWith(Result.success(1))
// 异常 or 失败
it.resumeWith(Result.failure(RuntimeException("异常")))
// 对于上述的ktx扩展
// 扩展函数,对于resumeWith-success的封装
it.resume(1)
// 扩展函数,对于resumeWith-failure的封装
it.resumeWithException(RuntimeException("异常"))
}
suspendCancellableCoroutine
对比前者多了 cancel
,即在内部取消此协程,取消时会抛出异常 CancellationException
。
后记
本文是比较简单入门的一篇文章,也是回复其他同学后,做的一个记录。虽然对我们而言,看着的确很简单,但在开始的路上,有问题并提出来总是好的。