Reusing closed Kotlin's Channel - kotlin

I want to use Channel as a Queue, but I need to clear it periodically. I didn't found clear method for the Channel and I make workaround with Channel.cancel and create new Channel, but it looks bad.
The question is:
How can I implement using Kotlin's channel as a queue with cleaning? Recreating a channel looks not so good...
Simplified context.
I have methods called by an external system: enqueue(id: Int) and cancel() and I don't have access to system that invokes these methods (React native methods in my case).
enqueue(id: Int) - enqueue id into the queue for processing (only one item at time can be processed) and starts processing the queue if is not started before.
cancel() - cancel pending processing but allow finishing current processing for current processing item.
My Processor is a singleton and enqueue(id: Int) can be called multiple times before canceling (to add items into queue) and after (for new processing).
My solution is to use channel as a queue and consume its items as a flow. cancel() will cancel the channel that allow current item processint to finish.
The problem is that after channel.cancel() channel is closed and I need to create new channel that is not so beautiful.
fun main() = runBlocking<Unit> {
val processor = Processor()
repeat(3) { processor.enqueue(it) }
delay(150)
processor.cancelPending()
delay(500)
println("Run processing one more time.")
repeat(3) { processor.enqueue(it) }
delay(500)
}
class Processor : CoroutineScope by CoroutineScope(Dispatchers.Default) {
private var channel = Channel<Int>(50)
private var processJob: Job? = null
fun enqueue(id: Int) {
channel.offer(id)
if (processJob?.isActive == true) return
processJob = launch {
channel.consumeAsFlow().collect { process(it) }
}
}
private suspend fun process(id: Int) {
delay(100)
println("[$id] processed.")
}
fun cancelPending() {
println("Cancel.")
channel.cancel()
channel = Channel(50)
}
}

Related

How to use callbackFlow within a flow?

I'm trying to wrap a callbackFlow within an outer flow - there are items I'd like to emit from the outer flow, but I've got an old callback interface, which I'd like to adapt to Kotlin flow. I've looked at several examples of usage of callbackFlow but I can't figure out how to properly trigger it within another flow.
Here's an example:
class Processor {
fun start(processProgress: ProcessProgressListener) {
processProgress.onFinished() //finishes as soon as it starts!
}
}
interface ProcessProgressListener {
fun onFinished()
}
//main method here:
fun startProcess(processor: Processor): Flow<String> {
val mainFlow = flow {
emit("STARTED")
emit("IN_PROGRESS")
}
return merge(processProgressFlow(processor), mainFlow)
}
fun processProgressFlow(processor: Processor) = callbackFlow {
val listener = object : ProcessProgressListener {
override fun onFinished() {
trySend("FINISHED")
}
}
processor.start(listener)
}
The Processor takes a listener, which is triggered when the process has finished. When that happens, I would like to emit the final item FINISHED.
The way I invoke the whole flow is as follows:
runBlocking {
startProcess(Processor()).collect {
print(it)
}
}
But, I get no output whatsoever. If I don't use the megre and only return the mainFlow, however, I do get the STARTED and IN_PROGRESS items though.
What am I doing wrong?
You forgot to call awaitClose in the end of callbackFlow block:
fun processProgressFlow(processor: Processor) = callbackFlow<String> {
val listener = object : ProcessProgressListener {
override fun onFinished() {
trySend("FINISHED")
channel.close()
}
}
processor.start(listener)
/*
* Suspends until 'channel.close() or cancel()' is invoked
* or flow collector is cancelled (e.g. by 'take(1)' or because a collector's coroutine was cancelled).
* In both cases, callback will be properly unregistered.
*/
awaitClose { /* unregister listener here */ }
}
awaitClose {} should be used in the end of callbackFlow block.
Otherwise, a callback/listener may leak in case of external cancellation.
According to the callbackFlow docs:
awaitClose should be used to keep the flow running, otherwise the channel will be closed immediately when block completes. awaitClose argument is called either when a flow consumer cancels the flow collection or when a callback-based API invokes SendChannel.close manually and is typically used to cleanup the resources after the completion, e.g. unregister a callback. Using awaitClose is mandatory in order to prevent memory leaks when the flow collection is cancelled, otherwise the callback may keep running even when the flow collector is already completed. To avoid such leaks, this method throws IllegalStateException if block returns, but the channel is not closed yet.

Will channel keep my coroutine running if it doesn't receive any values by channel.send()?

I start to use Channel in kotlinx.coroutines.channels with android and I am puzzled about the lifetime of my coroutineScope when using channel.
val inputChannel = Channel<String>()
launch(Dispatchers.Default) {
// #1
println("start #1 coroutine")
val value = inputChannel.receive()
println(value)
}
launch(Dispatchers.Default) {
inputChannel.send("foo")
}
It seems that if there is no value sent from inputChannel, inputChannel.receive() will never return a value and println(value) will not run, only "start #1 coroutine" will be printed.
My question is what happened to my #1 coroutine when inputChannel receives nothing? Does it run into a while(true) loop and keep waiting? If it does, will it run forever?
No, it will not run in "while(true)" loop.
Rather Coroutine#1 will get suspended at the line "inputChannel.receive()"
More details at
https://kotlinlang.org/docs/reference/coroutines/channels.html#buffered-channels
Regarding the "Lifetime" of CoroutineScope, it should be managed explicitly based on the Scenario.
For eg, in below "MyNotificationListener Service", the CoroutineScope is tied to the LIFECYCLE of the SERVICE i.e. the Coroutines are launched in "onCreate()" and cancelled in "onDestroy()"
class MyNotificationListener : NotificationListenerService() {
private val listenerJob = SupervisorJob()
private val listenerScope = CoroutineScope(listenerJob + Dispatchers.Default)
override fun onCreate() {
// Launch Coroutines
listenerScope.launch {
}
}
override fun onDestroy() {
// Cancel the Coroutines
listenerJob.cancel()
}
}

Coroutines how to wait for the data and then continue process

I'm learning coroutines with kotlin, and I have problem how process can wait until process 1 finished then it continue to process 2, from my sample below I have object Network which access API server using getNews(it's running well and get the data)
I called this getNews from refreshNews using asynch - await, with the purpose it wait for the data then it continue running, but "The program Not Wait", it just running process 2 then process 1 finish, so I cannot capture data from API in refresh news
// process 1 - calling api this running well can get the data see process 2
object Network {
var status : NewsApiStatus = NewsApiStatus.LOADING
private var viewModelJob = Job()
private val coroutineScope = CoroutineScope(viewModelJob + Dispatchers.Main)
fun getNews(filter: String, page: Int =1) : newsData? {
var allNews : newsData? = null
coroutineScope.launch {
RetrofitClient.instance.getAllNews(filter, page).enqueue(object: Callback<newsData>{
override fun onFailure(call: Call<newsData>, t: Throwable) {
status = NewsApiStatus.ERROR
}
override fun onResponse(
call: Call<newsData>,
response: Response<newsData>
) {
status = NewsApiStatus.DONE
var listResult = response.body()
if (listResult != null) {
if (listResult.data.isNotEmpty()) {
allNews = listResult
Timber.tag(TAG).i( "process 1 total allNews = ${allNews!!.data.size}")
}
}
}
})
}
return(allNews)
}
}
// process 2 - calling process 1 with runBlocking
fun refreshNews() = runBlocking{
val newsData = async {
Network.getNews("")
}
Timber.tag(TAG).i("proses 2 ${newsData.await()?.data?.size}")
// here I want newsData to wait until it has data
}
// this main program that call process 2
class NewsListViewModel(application: Application) : AndroidViewModel(application) {
init {
refreshNews()
}
}
launch returns a reference to the started job. You can use it to wait for the job to finish by calling join():
val job = GlobalScope.launch { // launch a new coroutine and keep a reference to its Job
// ...
}
runBlocking {
job.join() // wait until child coroutine completes
}
Currently, your getNews() launches a coroutine and immediately returns. allNews isn't initialised at that point yet.
You need to either call job.join() inside getNews() (would make it blocking), or use async inside getNews() and return its result if you want to keep it asynchronous (you'd need to take the result differently from your http client as you won't be able to initialise the variable declared outside).
It's worth to go through the official coroutine docs:
https://kotlinlang.org/docs/reference/coroutines/basics.html
https://kotlinlang.org/docs/tutorials/coroutines/coroutines-basic-jvm.html

Emit/Send Flow Values into BroadcastChannel

been pretty stuck on an issue with Kotlin flows/channels today. Essentially I want to take the values emitted from a flow, and immediately send them in a channel. We then subscribe to that channel as a flow via an exposed method. The use case here is to have a channel subscription that is always live and a flow that can be turned on and off independently.
private val dataChannel = BroadcastChannel<Data>(1)
suspend fun poll() {
poller.start(POLLING_PERIOD_MILLISECONDS)
.collect {
dataChannel.send(it)
}
}
suspend fun stopPoll() {
poller.stop()
}
suspend fun subscribe(): Flow<Data> {
return dataChannel.asFlow()
}
The simple use case I have here is a poller which returns a channelFlow. Ideally I could then emit to the channel in the collect method. This doesn't seem to work though. My rookie coroutine thought is that because collect and send are suspending, the emissions gets suspended in collect and we get stuck.
Is there any built in functions for flow or channel that can handle this or any other way to achieve this behavior?
For your case you can try to use hot stream of data SharedFlow instead of a Channel:
private val dataFlow = MutableSharedFlow<String>(extraBufferCapacity = 1)
suspend fun poll() {
poller.start(POLLING_PERIOD_MILLISECONDS)
.collect {
dataFlow.tryEmit(it)
}
}
suspend fun stopPoll() {
poller.stop()
}
fun subscribe(): Flow<Data> {
return dataFlow
}
tryEmit() - Tries to emit a value to this shared flow without suspending, so calling it will not suspend the collect block.

How to suspend kotlin coroutine until notified

I would like to suspend a kotlin coroutine until a method is called from outside, just like the old Java object.wait() and object.notify() methods. How do I do that?
Here: Correctly implementing wait and notify in Kotlin is an answer how to implement this with Kotlin threads (blocking). And here: Suspend coroutine until condition is true is an answer how to do this with CompleteableDeferreds but I do not want to have to create a new instance of CompleteableDeferred every time.
I am doing this currently:
var nextIndex = 0
fun handleNext(): Boolean {
if (nextIndex < apps.size) {
//Do the actual work on apps[nextIndex]
nextIndex++
}
//only execute again if nextIndex is a valid index
return nextIndex < apps.size
}
handleNext()
// The returned function will be called multiple times, which I would like to replace with something like notify()
return ::handleNext
From: https://gitlab.com/SuperFreezZ/SuperFreezZ/blob/master/src/superfreeze/tool/android/backend/Freezer.kt#L69
Channels can be used for this (though they are more general):
When capacity is 0 – it creates RendezvousChannel. This channel does not have any buffer at all. An element is transferred from sender to receiver only when send and receive invocations meet in time (rendezvous), so send suspends until another coroutine invokes receive and receive suspends until another coroutine invokes send.
So create
val channel = Channel<Unit>(0)
And use channel.receive() for object.wait(), and channel.offer(Unit) for object.notify() (or send if you want to wait until the other coroutine receives).
For notifyAll, you can use BroadcastChannel instead.
You can of course easily encapsulate it:
inline class Waiter(private val channel: Channel<Unit> = Channel<Unit>(0)) {
suspend fun doWait() { channel.receive() }
fun doNotify() { channel.offer(Unit) }
}
It is possible to use the basic suspendCoroutine{..} function for that, e.g.
class SuspendWait() {
private lateinit var myCont: Continuation<Unit>
suspend fun sleepAndWait() = suspendCoroutine<Unit>{ cont ->
myCont = cont
}
fun resume() {
val cont = myCont
myCont = null
cont.resume(Unit)
}
}
It is clear, the code have issues, e.g. myCont field is not synchonized, it is expected that sleepAndWait is called before the resume and so on, hope the idea is clear now.
There is another solution with the Mutex class from the kotlinx.coroutines library.
class SuspendWait2 {
private val mutex = Mutex(locaked = true)
suspend fun sleepAndWait() = mutex.withLock{}
fun resume() {
mutex.unlock()
}
}
I suggest using a CompletableJob for that.
My use case:
suspend fun onLoad() {
var job1: CompletableJob? = Job()
var job2: CompletableJob? = Job()
lifecycleScope.launch {
someList.collect {
doSomething(it)
job1?.complete()
}
}
lifecycleScope.launch {
otherList.collect {
doSomethingElse(it)
job2?.complete()
}
}
joinAll(job1!!, job2!!) // suspends until both jobs are done
job1 = null
job2 = null
// Do something one time
}