android kotlin 协程(四) 协程间的通信

android kotlin 协程(四) 协程间的通信

学完本篇你将会了解到:

  • channel
  • produce
  • actor
  • select

先来通过上一篇的简单案例回顾一下挂起于恢复:

fun main() {
    val waitTime = measureTimeMillis {
        runBlocking<Unit> {

            println("main start")            // 1   // 调度前
            launch {
                println("launch 1 start")    // 2   // 调度后(执行前)
                delay(1000)                                 // 延迟1s (不会阻塞兄弟协程)
                println("launch 1 end")      // 3
            }

            println("main mid")              // 4   // 调度前

            launch {
                println("launch 2 start")    // 5   // 调度后执行
                delay(500)                                  // 延迟0.5s (不会阻塞兄弟协程)
                println("launch 2 end")      // 6
            }
            println("main end")              // 7   // 调度前
        }
    }

    println("等待时间:${waitTime}")
}

通过上一篇我们知道了在协程中,

是会先执行调度前的代码,然后会执行调度后的代码, 直到调度后的时候,才会真正的执行到协程体中

所以这段代码的执行顺序为:

1,4,7,2,5,6,3

launch{} 中的lambda表达式 是一个suspend 函数标记的,所以始终是异步的,并不会阻塞兄弟协程

所以等待时间 约等于 1000

这里为什么说是约等于呢? 因为创建协程等一系列操作会稍微耗时一点,直接取整即可!

Channel

send / receive

channel是用来协程之前通信的,例如现在有一个需求,B协程需要使用A协程中的某个值,那么就用到了channel

先来看个最简单的例子

可以看出,A协程可以完成发送,并且B协程也可以完成接受

如果说A协程是一个网络接口,会返回数据,此时B协程是否还会等待A协程数据返回呢?

可以看出,即使是A协程会延迟2s,那么B协程也会等待A协程返回

如果说,A协程现在有3条数据要发送,B协程是否会接受3条呢?

那么就要介绍 Channel()的第一个参数了:

  • capacity 通道容量

channel 类似于一个阻塞队列(BlockingQueue), 默认是只缓存1条数据,只要不取,那么新的数据就无法加入到容器中

当send第二条数据的时候, 发现并没有receive() 来取第二条数据,所以就会出现一直挂起的效果

此时我们只需要让channel通道中容量变大,多存放几条数据即可

例如这样:

如果说,我们不想改变通道容量的大小,并且, 还要不让他挂起,那么就要介绍 channel的第二个参数了:

  • onBufferOverflow

从名字也可以看出,这是缓冲区溢出策略,一共有三种状态

  • BufferOverflow.SUSPEND: 挂起策略,当send不进去数据的时候,始终挂起,等待 receive() [默认]
  • BufferOverflow.DROP_OLDEST: 当要溢出的时候,删除缓冲区中最旧的值
  • BufferOverflow.DROP_LASTEST: 当要溢出的时候,删除缓冲区中最新的值

还是上面的例子,我们将容量设置为1, 往 channel中send 3条数据来看看效果

BufferOverflow.DROP_OLDEST BufferOverflow.DROP_LASTEST

目前这些代码应该很好理解!

trySend / tryReceive

在新版的channel更新的api中,还增添了一系列 tryXXapi

来看一段代码:

  • trySend() 尝试向channel中发送数据。这个函数会立即返回一个结果,表明是否成功将元素发送到通道中。如果通道已满,它会立即返回一个Failure类型的结果,否则会返回一个Success类型的结果。一般来说,生产者协程使用 trySend 函数来尝试将数据发送到通道中,不会阻塞协程,同时可以通过返回结果来判断是否成功发送数据。
  • tryReceive() 这个函数会立即返回一个结果,表明是否成功从通道中接收到元素。如果通道已空,它会立即返回一个Failure类型的结果,否则会返回一个Success类型的结果。一般来说,消费者协程使用 tryReceive 函数来尝试从通道中接收数据,不会阻塞协程,同时可以通过返回结果来判断是否成功接收数据。
// TODO =================== trySend / tryReceive ======================
fun main() = runBlocking<Unit> {
    // 用来协程间的通信
    val channel = Channel<String>(capacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
    println("main start")

    launch { // A协程
//        channel.close()
        val trySend = channel.trySend("A协程发送数据 1")
        if (trySend.isSuccess) {
            println("channel 发送成功")
        } else if (trySend.isClosed) {
            println("channel 关闭了")
        } else if (trySend.isFailure) {
            println("channel 发送失败")
        }
    }.join() // A协程必须执行完,通道有数据了之后才能取

        val tryReceive = channel.tryReceive()
        if (tryReceive.isSuccess) {
            println("tryReceive 接收到了数据:${tryReceive.getOrNull()}")
        } else if (tryReceive.isClosed) {
            println("tryReceive 关闭了")
        } else if (tryReceive.isFailure) {
            println("tryReceive 发送失败")
        }

    println("main end")
}

运行结果:

还有一些比较老的方法例如:

  • offer / poll 等一些淘汰的方法就不说了,

onSend / onReceive

还有最后一种发送,获取数据的方式,这种方式是通过select 选择器来实现的,先来看代码

//// TODO =================== onSend / onReceive ======================
fun main() = runBlocking<Unit> {
    // 用来协程间的通信
    val channel = Channel<String>(capacity = 5, onBufferOverflow = BufferOverflow.SUSPEND)
    println("main start")

    launch {    // A 协程 发送数据
        channel.send("send发送数据 ")
        channel.trySend("trySend发送数据")
    }

    //  select 接收数据 默认会挂起,等待数据返回
    select {
        channel.onReceive {
            println("onReceive:$it")
        }
    }
    println("result ${channel.receive()}")

    channel.invokeOnClose {
        println("channel close ")
    }

    println("main end")
}

运行结果:

select作用不止这些,目前了解可以接受即可,下面会重点提到!

这里有一个小知识:

如果看到有这种Select开头的,基本都是要写到select{} 中才能使用

运行结果:

select 下面会提到,这里就不重点说了.

在实际开发中,对于我来说,channel用的还是比较少, 我感觉这玩意比较坑,一般情况下,要实现2个协程通信,我会采用flow

例如这样:

这篇重点不是flow,这里就不多说了!

produce / actor

produce

produce意为生产者, 其本质就是对协程和channel的一层封装,

它返回一个 ReceiveChannel 对象,这个对象可以用于在其他协程中消费 生产者协程产生的数据。

使用很简单

:

api还是调用的channel的,对我们来说只是省略了, new Channel() 的过程,这里就不多说了

actor

actor 与produce正好相反

actor本质也是对协程与channel的封装, 它会返回一个SendChannel对象,这个对象用来给协程体发送数据

select

定义: 一旦某个挂起函数返回结果,select 结构就会立即返回该函数的结果,而其他仍在等待的挂起函数将会被取消。

注意点: select 只能用于挂起函数(即使用 suspend 修饰的函数)。另外,select 的选择器表达式中每个分支都应该返回相同类型的值,否则会编译报错。

简单的说就是, select 可以找到哪一个协程执行最快, 吧执行最快的结果返回,其他执行慢的,或者没有执行的协程全部关闭!

假设我们现在有一个实际的应用场景:

在实际开发中,我们需要请求接口, 请求接口的之前需要判断是否有缓存,

如果有缓存,就使用缓存数据

但是,如果请求接口比读取缓存数据还快,那么我们就用请求出来的数据

一般情况下缓存永远比请求数据快,这里就举个例子

select不仅可以监听 async的返回, 还有很多用处,例如可以监听协程是否执行完, 并且返回最快执行完的协程

来看看代码:

完整代码

下篇预告:

  • suspendCoroutine{}
  • suspendCancellableCoroutine{}
  • suspend 与 continuation与状态机器
  • 不通过协程运行 suspend函数

原创不易,您的点赞就是对我最大的帮助!

作者:史大拿
链接:https://juejin.cn/post/7202119664976248869

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容