Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 830
Nemo Avatar asked Mar 29 '19 16:03

Nemo


3 Answers

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) }
}
like image 145
Alexey Romanov Avatar answered Dec 04 '22 02:12

Alexey Romanov


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()
  }
}
like image 23
Eugene Petrenko Avatar answered Dec 04 '22 03:12

Eugene Petrenko


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
}
like image 33
funct7 Avatar answered Dec 04 '22 03:12

funct7