Coroutines basics
This section covers basic coroutine concepts.
Your first coroutine
A coroutine is an instance of a suspendable computation. It is conceptually similar to a thread, in the sense that it takes a block of code to run that works concurrently with the rest of the code. However, a coroutine is not bound to any particular thread. It may suspend its execution in one thread and resume in another one.
Coroutines can be thought of as light-weight threads, but there is a number of important differences that make their real-life usage very different from threads.
Run the following code to get to your first working coroutine:
You will see the following result:
Let's dissect what this code does.
launch is a coroutine builder. It launches a new coroutine concurrently with the rest of the code, which continues to work independently. That's why Hello
has been printed first.
delay is a special suspending function. It suspends the coroutine for a specific time. Suspending a coroutine does not block the underlying thread, but allows other coroutines to run and use the underlying thread for their code.
runBlocking is also a coroutine builder that bridges the non-coroutine world of a regular fun main()
and the code with coroutines inside of runBlocking { ... }
curly braces. This is highlighted in an IDE by this: CoroutineScope
hint right after the runBlocking
opening curly brace.
If you remove or forget runBlocking
in this code, you'll get an error on the launch call, since launch
is declared only on the CoroutineScope:
The name of runBlocking
means that the thread that runs it (in this case — the main thread) gets blocked for the duration of the call, until all the coroutines inside runBlocking { ... }
complete their execution. You will often see runBlocking
used like that at the very top-level of the application and quite rarely inside the real code, as threads are expensive resources and blocking them is inefficient and is often not desired.
Structured concurrency
Coroutines follow a principle of structured concurrency which means that new coroutines can only be launched in a specific CoroutineScope which delimits the lifetime of the coroutine. The above example shows that runBlocking establishes the corresponding scope and that is why the previous example waits until World!
is printed after a second's delay and only then exits.
In a real application, you will be launching a lot of coroutines. Structured concurrency ensures that they are not lost and do not leak. An outer scope cannot complete until all its children coroutines complete. Structured concurrency also ensures that any errors in the code are properly reported and are never lost.
Extract function refactoring
Let's extract the block of code inside launch { ... }
into a separate function. When you perform "Extract function" refactoring on this code, you get a new function with the suspend
modifier. This is your first suspending function. Suspending functions can be used inside coroutines just like regular functions, but their additional feature is that they can, in turn, use other suspending functions (like delay
in this example) to suspend execution of a coroutine.
Scope builder
In addition to the coroutine scope provided by different builders, it is possible to declare your own scope using the coroutineScope builder. It creates a coroutine scope and does not complete until all launched children complete.
runBlocking and coroutineScope builders may look similar because they both wait for their body and all its children to complete. The main difference is that the runBlocking method blocks the current thread for waiting, while coroutineScope just suspends, releasing the underlying thread for other usages. Because of that difference, runBlocking is a regular function and coroutineScope is a suspending function.
You can use coroutineScope
from any suspending function. For example, you can move the concurrent printing of Hello
and World
into a suspend fun doWorld()
function:
This code also prints:
Scope builder and concurrency
A coroutineScope builder can be used inside any suspending function to perform multiple concurrent operations. Let's launch two concurrent coroutines inside a doWorld
suspending function:
Both pieces of code inside launch { ... }
blocks execute concurrently, with World 1
printed first, after a second from start, and World 2
printed next, after two seconds from start. A coroutineScope in doWorld
completes only after both are complete, so doWorld
returns and allows Done
string to be printed only after that:
An explicit job
A launch coroutine builder returns a Job object that is a handle to the launched coroutine and can be used to explicitly wait for its completion. For example, you can wait for completion of the child coroutine and then print "Done" string:
This code produces:
Coroutines are light-weight
Coroutines are less resource-intensive than JVM threads. Code that exhausts the JVM's available memory when using threads can be expressed using coroutines without hitting resource limits. For example, the following code launches 50,000 distinct coroutines that each waits 5 seconds and then prints a period ('.') while consuming very little memory:
If you write the same program using threads (remove runBlocking
, replace launch
with thread
, and replace delay
with Thread.sleep
), it will consume a lot of memory. Depending on your operating system, JDK version, and its settings, it will either throw an out-of-memory error or start threads slowly so that there are never too many concurrently running threads.