Kotlin协程Flow与Channel对比

前言

fun main() {
    runBlocking {
        val flow = flow {
            emit("emit")
        }
        flow.collect{
            log("collect$it")
        }
    }
}

上游和下游属于同一个线程里。

  1. 操作符,即函数
  2. 上游,通过构造操作符创建
  3. 下游,通过末端操作符构建

只有下游才能通知上游放水,Flow属于冷流。生产数据的模块将生产过程封装到flow的上游里,最终创建了flow对象。

Channel核心原理与使用场景

Flow比较被动,在没有收集数据之前,上下游互不感知,管道并没有建立起来。

场景:需要将管道提前建立起来,在任何时候都可以在上游生产数据,在下游取数据,此时上下游可以感知的。

fun main() {
    // 提前建立通道/管道
    val channel = Channel()
    GlobalScope.launch {
        // 上游放水
        delay(200)
        val data = "放水了"
        log("上游:$data")
        channel.send(data)
    }
    GlobalScope.launch{
        val data = channel.receive()
        log("下游收到:$data")
    }
    // 防止父线程过早退出
    Thread.sleep(250)
}
输出:
[Thread[DefaultDispatcher-worker-1 @coroutine#1,5,main]] 上游:放水了
[Thread[DefaultDispatcher-worker-2 @coroutine#2,5,main]] 下游收到:放水了

先建立管道;往管道里放数据;从管道里取数据;

  1. 创建Channel
  2. 往Channel里放数据(生产)
  3. 从Channel里取数据(消费)

与Flow不同,生产者、消费者可以往Channel里存放/取出数据,只是能进行有效的存放,能否成功取出需要根据Channel状态确定。

Channel最大特点:

  1. 生产者、消费者访问Channel线程安全的,不管生产者和消费者在哪个线程,他们都能线程安全的存取数据
  2. 数据只能被消费一次,上游发送了一条数据,只要下游消费了数据,则其他下游将不会拿到此数据。

Flow切换线程的始末

场景:需要在flow里进行耗时操作(网络请求),外界拿到flow对象后等待收集数据即可。

fun main() {
    runBlocking { 
        val flow = flow { 
            thread { 
                Thread.sleep(3000)
                // 这里编译不过
                emit("emit")
            }
        }
    }
}

emit是挂起函数,需要在协程作用域里调用。

fun main() {
    runBlocking {
        val flow = flow {
            val coroutineScope = CoroutineScope(Job() + Dispatchers.IO)
            coroutineScope.launch {
                Thread.sleep(3000)
                // 这里运行报错:检测到在另一个线程里发射数据,这种行为不是线程安全的因此被禁止了
                emit("emit")
            }
        }
        flow.collect {
            log("collect:$it")
        }
    }
    // 防止父线程过早退

你可能感兴趣的:(kotlin,java,算法)