前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Kotlin | 从线程到协程,你是否还存在 [同步] 上的使用疑问

Kotlin | 从线程到协程,你是否还存在 [同步] 上的使用疑问

原创
作者头像
Petterp
发布2022-03-01 11:56:51
1.4K0
发布2022-03-01 11:56:51
举报
文章被收录于专栏:JetPack

Kotlin | 从线程到协程,你是否还存在理解上的疑问

Kotlin笔记相关插图的副本
Kotlin笔记相关插图的副本

引言

在2022的今天,对于一个 Android 开发同学,如果你使用 Kotlin 作为主要开发语言,那么协程是必不可缺的 异步框架 。不过对于初学者来说,有时候依然存在一些理解问题或者使用上的不解。毕竟我们用了那么多年的回调与线程,突然转变思想,的确需要过程。

本文将结合实际中其他同学遇到的问题来讲讲,从线程到协程,初学者对于 `[同步]` 的理解疑问。

背景

事情源自这样,今天早上在群里,发现有同学问到了这样一个问题:

协程A:开启一个等待页面,wait,等到B完成后显示成功 协程B:与下位机通讯,等到下位机回复成功后,通知A协程 notify

具体对话图示如下:

image-20220228160042537
image-20220228160042537

这个同学的想法是:

开启两个协程,协程A开启一个等待页面,然后在这里 wait 等待;等协程B这边执行成功后,再通知协程A去刷新。

作为过来人,我们不难第一反应,协程默认不就是同步吗,直接 suspend 就完了啊?为什么要通知呢?不是很麻烦吗?

解决这个问题很简单,但我的第一反应是,他似乎理解错了协程中的同步?但反过来又仔细一想,这个同学为什么能存在疑问,似乎我也曾问过,为什么不可以等待另一个job来通知我完成了呢?所以我更想告诉他为什么要这样写?

对于初使用协程而言,我们的想法应该怎样转变,这也即本文的主章:

面对协程,我们应该怎样去接受解决思路的转变

解决方法

在阐述 [莫须有] 的思想之前,我先写出下面的不同解法,以便大家更好的体会差异:

1. 线程写法

定义两个线程,线程A开始,然后 wait 等待,线程B执行逻辑,成功后,调用线程A notify.

代码语言:javascript
复制
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. 协程

image-20220228171355344
image-20220228171355344

解析

Android 官网中,对协程的描述如下:

协程是一种并发设计模式,您可以在 Android 平台上使用它来简化 异步执行 的代码。

说简单点就是,在协程的世界中,一切都是同步,按顺序进行。即一步接一步,我们等待上一步的结果,然后决定是否继续执行下一步。

综合对比上述的解法来看:

  1. 线程写法:我们需要调用 await ,这将使得正在运行的线程[阻塞],对我们的性能造成了影响;
  2. 回调写法:我们不再阻塞线程,但我们逻辑更复杂化,如果存在多个回调,这将提高阅读成本;
  3. 协程写法:我们提供了两种不同的写法,即是否需要改善相应方法中的回调。
    • 前者在执行任务B时,我们切换到了 IO协程 ,并最终将状态返回,接下来,我们判断,如果获得的state是我们想要的写法,就继续操作;
    • 后者在执行任务B时,利用了suspendCoroutine 函数,我们可以将一些回调的代码借此改为协程的同步写法,从而获得与前者一致的体验;

所以协程具有如下的基本特点:

更轻量简化异步代码

而面对难解决的异步代码时,我们首要的不应该考虑如何去通知,而是看看能不能将任务拆分,比如将原有需要通知的这一步拆为三步走:

在非协程的世界,我们可能想,先执行任务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

后记

本文是比较简单入门的一篇文章,也是回复其他同学后,做的一个记录。虽然对我们而言,看着的确很简单,但在开始的路上,有问题并提出来总是好的。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Kotlin | 从线程到协程,你是否还存在理解上的疑问
    • 引言
      • 背景
        • 解决方法
          • 1. 线程写法
          • 2. 接口回调
          • 3. 协程
        • 解析
          • 扩展
            • suspend
            • 将回调代码转为协程
          • 后记
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档