supermar10
supermar10

Reputation: 180

Kotlin coroutine delay waits for couroutine completion/fire and forget

I have the following code.

fun main(args: Array<String>) {
    runBlocking {
        while (true) {
            launch {
                println("Taking measurement")
                val begin = System.currentTimeMillis()
                while (System.currentTimeMillis() - begin < 20000) {
                    val test = 5 + 5
                }
                println("Took measurement")
            }
            println("After launch")
            delay(1000)
            println("After delay")
        }
    }
}

I would expect the output of that to be twice as many "After launch"s and "After delay"s in the first 20 seconds. SO the output should look something like this:

After launch
Taking measurement
After delay
After launch
Taking measurement
After delay
After launch
Taking measurement
After delay
After launch
Taking measurement
After delay
After launch
Taking measurement
... and then after 20s the first took measurements

But rather it looks like this and I can not wrap my head around to why it is not working.

After launch
Taking measurement
Took measurement
After delay
After launch
Taking measurement

What I basically want to achieve is a fire and forget. I want to start the code in launch and then 1 second after that I want to start it again.... The result of that code will be saved by that code, so I do not need any data back.

Any tips on why this is not working?

Upvotes: 0

Views: 2095

Answers (1)

Tenfour04
Tenfour04

Reputation: 93834

This is because the default dispatcher of runBlocking is a single-threaded dispatcher that uses the same thread that runBlocking was called on. Since it is single-threaded, it cannot run the blocking portions of your coroutine concurrently. If you use runBlocking(Dispatchers.Default) or launch(Dispatchers.Default) to avoid running in the single-threaded dispatcher, it will behave as you had expected.

However, the only reason you ran into this surprise in the first place is that you are trying to do blocking work directly in a coroutine, which you're not supposed to do. You should not do blocking work in a coroutine without wrapping it in withContext or a suspend function that uses withContext. If you replace your blocking loop in the coroutine with a delay(1000) suspend function call to simulate doing work in a non-blocking way, it will behave as you had expected.

When you do blocking work in a coroutine, you should either wrap it in a withContext call like this:

withContext(Dispatchers.Default) {
    while (System.currentTimeMillis() - begin < 20000) {
        val test = 5 + 5
    }
}

or break it out into a suspend function that uses withContext to perform the blocking work on an appropriate dispatcher:

suspend fun doLongCalculation() = withContext(Dispatchers.Default) {
    val begin = System.currentTimeMillis()
    while (System.currentTimeMillis() - begin < 20000) {
        val test = 5 + 5
    }
}

This is why replacing your blocking work with a delay suspend function call is an adequate simulation of doing long-running work. delay and the doLongCalculation() above are both proper suspend functions that do not block their caller or expect their caller to be using a specific dispatcher to function correctly.

Upvotes: 1

Related Questions