The Programmers Guide To Kotlin - Coroutines
Written by Mike James   
Monday, 07 June 2021
Article Index
The Programmers Guide To Kotlin - Coroutines
Launch
coroutineScope

Launch

To actually see a coroutine do something distinctly different, we need a slightly more complicated program and we need to use the launch method to create another coroutine and place it in the dispatcher’s queue. The launch method can only be used within a CoroutineScope object and as runBlocking inherits from CoroutineScope this is fine.

Our new program will create a second coroutine and add it to the default dispatcher’s queue:

import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking 
fun main() { println("main start") runBlocking { println("Coroutine1 start") launch { println("Coroutine2 start") for (i in 1..10) { print(i) } println(" Coroutine2 finishing") }
for (i in 1..20) { print(i) } println(" Coroutine1 finishing") } println("main stopped") }

When you run this, what you see is:

main start
Coroutine1 start
1234567891011121314151617181920 Coroutine1 finishing
Coroutine2 start
12345678910 Coroutine2 finishing
main stopped

You need to look a little carefully to see what this is telling you. Coroutine2 was added to the dispatcher’s queue before the for loop in Coroutine1 and yet its for loop is displayed after that loop. That is Coroutine2 was added to the queue before the rest of Coroutine1 completed and only started to run when it was finished. The launch method simply added Coroutine2 to the dispatcher’s queue and then Coroutine1 carried on.

Notice that the block of code that is passed to launch is automatically converted to a coroutine and is a suspend function. You can call other functions from within the block of code, or indeed any coroutine, but if the function is a non-suspend function, i.e. a “normal” function, it cannot contain any suspension points. If the coroutine calls a suspend function then it can contain suspension points. In general, coroutines should always call other suspend functions.

So, for example, the previous example can be rewritten to use an explicit function:

import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() {
    println("main start")
    runBlocking {
        println("Coroutine1 start")
        launch {
            co2()
        }
        for (i in 1..20) {
            print(i)
        }
        println(" Coroutine1 finishing")
    }
    println("main stopped")
}
suspend fun co2() {
    println("Coroutine2 start")
    for (i in 1..10) {
        print(i)
    }
    println(" Coroutine2 finishing")
}

In this case the suspend could be dropped from the co2 function as it contains no suspension points.

Delay & Yield

How can you suspend a coroutine? There are a number of methods that can be used to explicitly suspend execution of a coroutine but sometimes the system will force the suspension simply because it has to wait for something to happen. The simplest suspend method to use to demonstrate how the dispatcher works is delay, which will cause the coroutine to suspend for the specified number of milliseconds.

Using this we can show that if the first coroutine in our example suspends, then the second one gets to run:

import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() {
    println("main start")
    runBlocking {
        println("Coroutine1 start")
launch { co2() } for (i in 1..20) { print(i) delay(1) } println(" Coroutine1 finishing") } println("main stopped") }

Here the co2 function is unchanged. Notice that now we have a 1ms delay in the first coroutine’s for loop.

If you run this program what you see is:

main start
Coroutine1 start
1Coroutine2 start
12345678910 Coroutine2 finishing
234567891011121314151617181920 Coroutine1 finishing
main stopped

Notice that Coroutine1 starts and then prints 1. The delay suspends Coroutine1 which allows Coroutine2 to start and as this doesn’t have a suspension point it runs to completion and prints 1 to 10. When it finishes the dispatcher restarts the first coroutine, which continues where it left off.

If you increase the length of the for loop in Coroutine2 so that it takes longer than 1ms, you will still not see Coroutine1 restarted. Once a coroutine starts running it is only stopped, and another coroutine started, if it suspends.

The final demonstration is to add a call to delay for 1ms in the for loop of Coroutine2. In this case Coroutine2 suspends after each digit it prints and Coroutine1 is restarted. The result is that both coroutines get to run their for loops and the result is a mixed-up list of numbers from both coroutines:

main start
Coroutine1 start
1Coroutine2 start
12233445566778899101011 Coroutine2 finishing
121314151617181920 Coroutine1 finishing
main stopped

To summarize:

  • Coroutines are functions which can suspend their execution and can resume from where they left off.

  • Positions where they could suspend execution are known as suspension points.

  • Putting suspend in front of a function declaration does not convert it into an asynchronous function, it only permits it to have suspension points.

  • Coroutines are added to the dispatch queue without suspending the current coroutine that is placing them in the queue.

  • If a coroutine suspends then another coroutine in the queue is started using the same thread.

  • Coroutines are started in the order they are in the queue.

  • Coroutines run to completion unless they suspend.

In addition to delay you can also use yield, which suspends the coroutine without a time specified. The coroutine will restart as soon as its turn with the dispatcher comes around. That is, if a coroutine yields, the next coroutine in the queue is run until it suspends and then the next coroutine is run until the order wraps round and the original coroutine is restarted.



Last Updated ( Monday, 07 June 2021 )