Some Notes on Kotlin Coroutines
This post is written to dictate some opinionated explanation that dispels my confusion to Kotlin coroutines during learning.
suspend
keyword colors a function
The suspend
keyword in Kotlin colors a function, just like the async
keyword in Javascript and Python, to inform the compiler that it might execute some asynchronous tasks.
suspend
ing functions, such as the delay()
function, must only be invoked within another suspend
ing function.
suspend fun foo() {
delay(1000)
}
fun bar() {
delay(1000) // not allowed
}
async def foo():
await asyncio.sleep(1)
def bar():
await asyncio.sleep(1) # not allowed
Invocation of suspending functions are sequential by default
If two suspending functions fun1()
and fun2()
are invoked one followed with another, their invocations are sequentialized, i.e., only after fun1()
finished will fun2()
be started. This is in contrast with Javascript or Python, where async
-functions must be explicitly await
-ed to sequentialize.
suspend fun fun1() { delay(1000) }
suspend fun fun2() { delay(2000) }
suspend fun foo() {
fun1()
fun2()
// foo() finished in 3 seconds
}
async def fun1(): await asyncio.sleep(1)
async def fun2(): await asyncio.sleep(2)
async def foo():
fun1()
fun2()
# foo() returns immediately
In both of the preceding codes, fun1()
and fun2()
are invoked without additional syntax constructs. In Kotlin the invocations are sequential by default, and thus foo()
will return in 3 seconds. In Python, the coroutines created are leaked without any execution, causing foo()
to return immediately, and hopefully you will get a RuntimeWarning: coroutine 'fun1' was never awaited
as alert. To equalize them, the Python version should use explicit await
:
suspend fun fun1() { delay(1000) }
suspend fun fun2() { delay(2000) }
suspend fun foo() {
fun1()
fun2()
// foo() finished in 3 seconds
}
async def fun1(): await asyncio.sleep(1)
async def fun2(): await asyncio.sleep(2)
async def foo():
await fun1()
await fun2()
# foo() returns in 3 seconds
Such difference between Kotlin and other languages prevents the case of developers forgetting to write await
and leaking coroutines, which is a foundation of ergonomic structural concurrency.
suspend function scope != coroutine scope
In Kotlin, coroutine builders such as launch()
or async()
must be invoked within a coroutine scope, since they are effectively extension methods of class CoroutineScope
. However, a suspend function does not essentially form a coroutine scope, which means the following code is invalid:
suspend fun foo() {
launch { delay(1000) }
}
Instead, one should wrap the function with a coroutineScope
call to make use of the extensions:
suspend fun foo() {
suspend fun foo() = coroutineScope {
launch { delay(1000) }
}
suspend fun foo() {
launch { delay(1000) }
}
suspend fun foo() = coroutineScope {
launch { delay(1000) }
}
Apart from coroutineScope()
, other functions like launch()
, async()
or runBlocking()
also create coroutine scopes.
Coroutine scope is the effective way to structural concurrency
Coroutine scopes are hierarchical. When calling .launch()
or .async()
from a outer scope, a child scope is inherited and created from it, implicitly forming a tree-like invocation structure.
fun main(): Unit = runBlocking(CoroutineName("A")) {
launch(CoroutineName("B")) {
launch(CoroutineName("C")) {
delay(1000)
println(2)
}
println(1)
}.join()
launch(CoroutineName("D")) { println(3) }
}
In the above program, we explictly attributes the name of different coroutines in order to refer them crystally. The program will create a conceptual hierarchy as below
A --> B -> C
\-> D
Such hierarchy constrains the lifecycle of coroutines and therefore derives the structural concurrency. Specifically, we have –
Coroutine scopes are self-contained. A parental coroutine always waits for completion of all its children. Cancelling a parental coroutine recursively cancels all its children. The rules implies the program above would output like:
1 (after one second)
2
3
Since launch(CoroutineName("B")) {...}.join()
would block until its child coroutine-C
returns in one second. The self-containing property enables ergonomic cooperative job cancellation and ensures coroutines won’t be readily leaked 1.
- However, they could be leaked if, for example, you insist to use
GlobalScope.launch
.
Author: hsfzxjy.
Link: .
License: CC BY-NC-ND 4.0.
All rights reserved by the author.
Commercial use of this post in any form is NOT permitted.
Non-commercial use of this post should be attributed with this block of text.
OOPS!
A comment box should be right here...But it was gone due to network issues :-(If you want to leave comments, make sure you have access to disqus.com.