in my program i have multiple commands that i want...
# coroutines
w
in my program i have multiple commands that i want to execute (where a command is something that is triggered, and polled upon until it completes). only one command can be executed at a time (limitation on the remote system). If i was only doing one of these at a time i would just call my
suspend fun
and wait for the result. however i am calling multiple `suspend fun`s from throughout my code base. I’m trying to think of a way to group these together and allow them to queue since each of the call sites uses a separate context/scope. another requirement is that if a particular command completes, it can cancel all remaining commands that are queued up. my first attempt at it in the thread -->
Copy code
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext

fun main() = runBlocking {
    val lock = CloseableMutex()
    
    // from class A in scope A
    queuedLaunch(lock) {
        // create a command and poll on it ...
        println("hello")
        delay(2_000)
    }
    
    // from class B in scope B
    queuedLaunch(lock, terminateQueueAfterwards = true) {
        // create a command and poll on it ...
        println("hello 2")
        delay(1_000)
    }
    
    // from class C in scope C
    queuedLaunch(lock) {
        // create a command and poll on it ...
        println("hello 3")
        delay(3_000)
    }
}

fun CoroutineScope.queuedLaunch(
    mutex: CloseableMutex,
    context: CoroutineContext = EmptyCoroutineContext,
    terminateQueueAfterwards: Boolean = false,
    block: suspend CoroutineScope.() -> Unit,
) {
    launch(context) {
        // wait my turn
        mutex.withCancellableLock {
            // then execute
            block()
            if (terminateQueueAfterwards) {
                mutex.closed = true
            }
        }
    }
}

class CloseableMutex: Mutex by Mutex() {
    var closed = false

    suspend inline fun <T> withCancellableLock(owner: Any? = null, action: () -> T): T =
        withLock(owner) {
            if (closed) throw CancellationException()
            else action()
        }
}
and this outputs
Copy code
hello
hello 2
the
queuedLaunch
extension function seems a bit against the grain of normal coroutines usage. I’m just struggling to think of some other ways to achieve this - hoping for some suggestions 🙂
c
Other solutions: • create a single thread and use withContext to switch to it whenever you need to send something (expensive if you need to do this a lot) • create a channel, send requests to that channel when you need to send stuff, and have a single worker read from that channel and execute whatever you wanted