https://kotlinlang.org logo
Title
n

Niklas Gürtler

01/29/2021, 10:54 AM
Is there a common pattern for starting a coroutine, when you only ever want one or zero instances of it running? Something like
class MyActivity {
    var coroJob : Job? = null
    val coroScope = MainScope()
  
    // Start my coroutine, may be called from any thread
    fun startCoro () {
        // coroJob should only be accessed from main thread
        coroutineScope.launch (Dispatchers.Main) {
            if (coroJob == null) {
                coroJob = coroutineScope.launch (<http://Dispatchers.IO|Dispatchers.IO>) {
                    // Do the actual work here ...

                    withContext(Dispatchers.Main) {
                        coroJob = null
                    }
                }
            }
        }
    }  
}
But reducing the boiler plate?
t

tseisel

01/29/2021, 2:45 PM
You may use an actor coroutine:
val triggerChannel: SendChannel<Unit> = scope.actor<Unit> {
  for (unit in channel) {
    // Do stuff
  }
}

// Send params via the channel whenever you want to perform work.
triggerChannel.offer(Unit)
Using an actor you are guaranteed that only one instance of your task is running at the time. You may also tweak the actor's
capacity
to enqueue (or discard) work requests that were sent before previous work finished. Don't forget to close the channel when done with it!
n

Niklas Gürtler

01/29/2021, 3:09 PM
Ah nice idea! So the actor is permanently running but blocked as long as nothing is in queue, right?
t

tseisel

01/29/2021, 4:32 PM
Yes, the
for
loop suspends until something is pushed to the channel. It won't consume a thread while waiting, so it is completely safe to use
Dispatchers.Main
(unless you are directly calling blocking IO functions in the actor's body, of course).
n

Niklas Gürtler

01/29/2021, 4:35 PM
Ah right. I am doing IO so I'd use the IO dispatcher, and I need some post processing on the main thread. So I'm trying:
private val actor1 = (coroutineScope+<http://Dispatchers.IO|Dispatchers.IO>).actor<Unit> (capacity = Channel.CONFLATED) {
    for (u in channel) {
        val result = ... do stuff ...
        actor2.offer (result)
    }
}

private val actor2 = (coroutineScope+Dispatchers.Main).actor<Result> (capacity = Channel.CONFLATED) {
    for (t in channel)
        ... post process t ...
}


fun start () {
    actor1.offer (Unit);
}
t

tseisel

01/29/2021, 4:47 PM
Yes, that's the idea. I suggest that you use
actor2.send(result)
instead of
actor2.offer(result)
if you absolutely need each task to run post-processing. The key difference is that
offer
is not suspending and will drop the result if actor2 is busy, while
send
will suspend until actor2 is ready.
n

Niklas Gürtler

01/29/2021, 4:49 PM
Good point, yes
send
is indeed more suitable. Thanks!
g

gildor

01/31/2021, 12:28 PM
I wouldn't use actor for that, it will change api soon and just write simple abstraction on top of Job