Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin: How to wait for a coroutine from non-suspend without runBlocking?

Edit 2: I think I misunderstood the documentation. I read:

runBlocking

This function should not be used from a coroutine. It is designed to bridge regular blocking code to libraries that are written in suspending style, to be used in main functions and in tests.

To mean that I shouldn't use runBlocking() at all other than for main or tests. But I now realise I read it wrong, specifically this part:

It is designed to bridge regular blocking code to libraries that are written in suspending style

So it seems that runBlocking should be used in this scenario.

However I think I should fully grok the topic of contexts to see which contexts is best suited to pass to runBlocking in this case:

return runBlocking(???????) {
    job1.await() + job2.await()
}

Edit: Clearly I phrased the question badly as all attempts to answer it miss the actual question and the constraint I am posing. So let's try a different approach...

This works:

fun doSomething(): Int {
    val job1 = GlobalScope.async { calculateSomething() }
    val job2 = GlobalScope.async { calculateSomething() }
    return runBlocking {
        job1.await() + job2.await()
    }
}

suspend fun calculateSomething(): Int {
    delay(1000L)
    return 13
}

suspend fun calculateSomethingElse(): Int {
    delay(2000L)
    return 19
}

My question is: is there anyway I can achieve the same result:

  1. Without using runBlocking() at all.
  2. Without turning doSomething() into a suspend function.

?

In other words: is there anything I can put instead of ?????? to make the following work?

fun doSomething(): Int {
    val job1 = GlobalScope.async { calculateSomething() }
    val job2 = GlobalScope.async { calculateSomething() }
    return ????????? {
        job1.?????() + job2.?????()
    }
}

suspend fun calculateSomething(): Int {
    delay(1000L)
    return 13
}

suspend fun calculateSomethingElse(): Int {
    delay(2000L)
    return 19
}

I have a small utility method that runs any given external command and returns its output (i.e small wrapper around Java Process API):

class RunningCommand(private val proc: Process) {

    fun waitFor(): String {
        proc.outputStream.close()

        var output = ""
        val outputRdr = thread { output = proc.inputStream.bufferedReader().use { it.readText() } }
        var error = ""
        val errorRdr = thread { error = proc.errorStream.bufferedReader().use { it.readText() } }

        proc.waitFor()

        outputRdr.join()
        errorRdr.join()

        if (proc.exitValue() != 0) {
            throw RuntimeException("Command returned non-zero status: $output$error")
        }

        return output
    }
}

This code works correctly. However it creates two additional threads for each command execution. I wanted to avoid that by switching to coroutines. I was able to do this, but I had to use runBlocking:

class RunningCommand(private val proc: Process) {

    fun waitFor(): String {
        proc.outputStream.close()

        var output = ""
        val outputRdr = GlobalScope.async { output = proc.inputStream.bufferedReader().use { it.readText() } }
        var error = ""
        val errorRdr = GlobalScope.async { error = proc.errorStream.bufferedReader().use { it.readText() } }

        proc.waitFor()

        runBlocking {
            outputRdr.await()
            errorRdr.await()
        }

        if (proc.exitValue() != 0) {
            throw RuntimeException("Command returned non-zero status: $output${error}")
        }

        return output
    }
}

This code also works, but I read that runBlocking should only be used on main() methods and tests, i.e not meant to be used in this way. Peeking at its implementation it looks horrible and indeed looks like something one wouldn't want to call repeatedly from some utility method.

So my question is: how else am I supposed to bridge the gap between blocking code and coroutines? Or put differently, what is the correct way to wait for a suspend function from non-suspend code?

Or is it simply that my design is wrong, and to use Coroutines anywhere down the line I need to make the main() method runBlocking and essentially always be inside some coroutine scope?

like image 383
Amir Abiri Avatar asked Jan 26 '23 11:01

Amir Abiri


1 Answers

For any future travellers who make the same mistake as me - runBlocking is OK to use not just in main / tests - but also:

It is designed to bridge regular blocking code to libraries that are written in suspending style

Somehow I got the impression that it is evil to use for just some library function, but it is not.

like image 136
Amir Abiri Avatar answered Apr 30 '23 16:04

Amir Abiri