The Programmers Guide To Kotlin - Coroutines |
Written by Mike James | ||||
Monday, 07 June 2021 | ||||
Page 2 of 3
LaunchTo 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 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 & YieldHow 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") 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:
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 ) |