How to close the channel after all producer coroutines are done?

1.1k views Asked by At

Consider the following code:

import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.*

fun main() = runBlocking<Unit> {
    val channel = Channel<String>()
    launch {
        channel.send("A1")
        channel.send("A2")
        log("A done")
    }
    launch {
        channel.send("B1")
        log("B done")
    }
    launch {
        for (x in channel) {
            log(x)
        }
    }
}

fun log(message: Any?) {
    println("[${Thread.currentThread().name}] $message")
}

The original version has the receiver coroutine like that:

launch {
        repeat(3) {
            val x = channel.receive()
            log(x)
        }
    }

It expects only 3 messages in the channel. If I change it to the first version then I need to close the channel after all producer coroutines are done. How can I do that?

2

There are 2 answers

0
atarasenko On BEST ANSWER

A possible solution is to create a job that will wait for all channel.send() to finish, and call channel.close() in the invokeOnCompletion of this job:

import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.*

fun main() = runBlocking<Unit> {
    val channel = Channel<String>()
    launch {
      launch {
          channel.send("A1")
          channel.send("A2")
          log("A done")
      }
      launch {
          channel.send("B1")
          log("B done")
      }
    }.invokeOnCompletion {
        channel.close()
    }
    launch {
        for (x in channel) {
            log(x)
        }
    }
}

fun log(message: Any?) {
    println("[${Thread.currentThread().name}] $message")
}
0
Andrew Hall On

Building on the accepted answer - you can write an extension that encapsulates the scope of an open channel:

import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.toList

fun main() = runBlocking {
    val channel = Channel<String>()
    launch {
        produce(channel) {
            send("foo")
            send("bar")
            send("foobar")
        }
    }
    val asList = channel.receiveAsFlow()
        .toList()
    println(asList)
}

fun <T> CoroutineScope.produce(channel: Channel<T>, block: suspend Channel<T>.() -> Unit) =
        channel.run {
            launch(context = coroutineContext) { block() }
        }
        .invokeOnCompletion { channel.close() }