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

先来看个最简略的比如

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

能够看出,A协程能够完结发送,而且B协程也能够完结承受

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

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

能够看出,即使是A协程会推迟2s,那么B协程也会等候A协程回来

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

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

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

  • capacity 通道容量

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

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

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

例如这样:

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

假如说,咱们不想改动通道容量的巨细,而且, 还要不让他挂起,那么就要介绍 channel的第二个参数了:

  • onBufferOverflow

从姓名也能够看出,这是缓冲区溢出战略,一共有三种状况

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

仍是上面的比如,咱们将容量设置为1, 往 channel中send 3条数据来看看作用

BufferOverflow.DROP_OLDEST BufferOverflow.DROP_LASTEST
android kotlin 协程(四) 协程间的通信
android kotlin 协程(四) 协程间的通信

现在这些代码应该很好理解!

trySend / tryReceive

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

来看一段代码:

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

  • 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")
}

运转成果:

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

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

  • 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")
}

运转成果:

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

select作用不止这些,现在了解能够承受即可,下面会要点提到!

这儿有一个小知识:

假如看到有这种Select开头的,根本都是要写到select{} 中才能运用

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

运转成果:

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

select 下面会提到,这儿就不要点说了.

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

例如这样:

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

这篇要点不是flow,这儿就不多说了!

produce / actor

produce

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

它回来一个 ReceiveChannel 对象,这个对象能够用于在其他协程中消费 生产者协程发生的数据。

运用很简略

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

api仍是调用的channel的,对咱们来说只是省略了, new Channel() 的进程,这儿就不多说了

actor

actor 与produce正好相反

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

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

select

定义: 一旦某个挂起函数回来成果,select 结构就会当即回来该函数的成果,而其他仍在等候的挂起函数将会被撤销。

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

简略的说便是, select 能够找到哪一个协程履行最快, 吧履行最快的成果回来,其他履行慢的,或者没有履行的协程悉数封闭!

假设咱们现在有一个实践的运用场景:

在实践开发中,咱们需要恳求接口, 恳求接口的之前需要判断是否有缓存,

假如有缓存,就运用缓存数据

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

一般情况下缓存永远比恳求数据快,这儿就举个比如

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

select不仅能够监听 async的回来, 还有许多用处,例如能够监听协程是否履行完, 而且回来最快履行完的协程

来看看代码:

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

完好代码

下篇预告:

  • suspendCoroutine{}
  • suspendCancellableCoroutine{}
  • suspend 与 continuation与状况机器
  • 不经过协程运转 suspend函数

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