aboutsummaryrefslogtreecommitdiffstats
path: root/docs/topics/coroutines-basics.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/topics/coroutines-basics.md')
-rw-r--r--docs/topics/coroutines-basics.md371
1 files changed, 140 insertions, 231 deletions
diff --git a/docs/topics/coroutines-basics.md b/docs/topics/coroutines-basics.md
index c1c17581..5d9d0e6d 100644
--- a/docs/topics/coroutines-basics.md
+++ b/docs/topics/coroutines-basics.md
@@ -6,19 +6,27 @@ This section covers basic coroutine concepts.
## Your first coroutine
-Run the following code:
+A _coroutine_ is an instance of 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:
```kotlin
import kotlinx.coroutines.*
-fun main() {
- GlobalScope.launch { // launch a new coroutine in background and continue
+//sampleStart
+fun main() = runBlocking { // this: CoroutineScope
+ launch { // launch a new coroutine and continue
delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
println("World!") // print after delay
}
- println("Hello,") // main thread continues while coroutine is delayed
- Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive
+ println("Hello") // main coroutine continues while a previous one is delayed
}
+//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
@@ -29,49 +37,71 @@ fun main() {
You will see the following result:
```text
-Hello,
+Hello
World!
```
<!--- TEST -->
-Essentially, coroutines are light-weight threads.
-They are launched with [launch] _coroutine builder_ in a context of some [CoroutineScope].
-Here we are launching a new coroutine in the [GlobalScope], meaning that the lifetime of the new
-coroutine is limited only by the lifetime of the whole application.
+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.
-You can achieve the same result by replacing
-`GlobalScope.launch { ... }` with `thread { ... }`, and `delay(...)` with `Thread.sleep(...)`.
-Try it (don't forget to import `kotlin.concurrent.thread`).
+[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.
-If you start by replacing `GlobalScope.launch` with `thread`, the compiler produces the following error:
+[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 in the [CoroutineScope]:
```Plain Text
-Error: Kotlin: Suspend functions are only allowed to be called from a coroutine or another suspend function
+Unresolved reference: launch
```
-That is because [delay] is a special _suspending function_ that does not block a thread, but _suspends_ the
-coroutine, and it can be only used from a coroutine.
+The name of `runBlocking` means that the thread that runs it (in this case &mdash; 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
-## Bridging blocking and non-blocking worlds
+Coroutines follow a principle of
+**structured concurrency** which means that new coroutines can be only 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.
-The first example mixes _non-blocking_ `delay(...)` and _blocking_ `Thread.sleep(...)` in the same code.
-It is easy to lose track of which one is blocking and which one is not.
-Let's be explicit about blocking using the [runBlocking] coroutine builder:
+In the 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.
```kotlin
import kotlinx.coroutines.*
-fun main() {
- GlobalScope.launch { // launch a new coroutine in background and continue
- delay(1000L)
- println("World!")
- }
- println("Hello,") // main thread continues here immediately
- runBlocking { // but this expression blocks the main thread
- delay(2000L) // ... while we delay for 2 seconds to keep JVM alive
- }
+//sampleStart
+fun main() = runBlocking { // this: CoroutineScope
+ launch { doWorld() }
+ println("Hello")
}
+
+// this is your first suspending function
+suspend fun doWorld() {
+ delay(1000L)
+ println("World!")
+}
+//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
@@ -80,27 +110,39 @@ fun main() {
{type="note"}
<!--- TEST
-Hello,
+Hello
World!
-->
-The result is the same, but this code uses only non-blocking [delay].
-The main thread invoking `runBlocking` _blocks_ until the coroutine inside `runBlocking` completes.
+## Scope builder
+
+In addition to the coroutine scope provided by different builders, it is possible to declare your own scope using the
+[coroutineScope][_coroutineScope] builder. It creates a coroutine scope and does not complete until all launched children complete.
+
+[runBlocking] and [coroutineScope][_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][_coroutineScope] just suspends, releasing the underlying thread for other usages.
+Because of that difference, [runBlocking] is a regular function and [coroutineScope][_coroutineScope] is a suspending function.
-This example can be also rewritten in a more idiomatic way, using `runBlocking` to wrap
-the execution of the main 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:
```kotlin
import kotlinx.coroutines.*
-fun main() = runBlocking<Unit> { // start main coroutine
- GlobalScope.launch { // launch a new coroutine in background and continue
+//sampleStart
+fun main() = runBlocking {
+ doWorld()
+}
+
+suspend fun doWorld() = coroutineScope { // this: CoroutineScope
+ launch {
delay(1000L)
println("World!")
}
- println("Hello,") // main coroutine continues here immediately
- delay(2000L) // delaying for 2 seconds to keep JVM alive
+ println("Hello")
}
+//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
@@ -108,49 +150,43 @@ fun main() = runBlocking<Unit> { // start main coroutine
>
{type="note"}
-<!--- TEST
-Hello,
-World!
--->
-
-Here `runBlocking<Unit> { ... }` works as an adaptor that is used to start the top-level main coroutine.
-We explicitly specify its `Unit` return type, because a well-formed `main` function in Kotlin has to return `Unit`.
+This code also prints:
-This is also a way to write unit tests for suspending functions:
-
-<!--- INCLUDE
-import kotlinx.coroutines.*
--->
-
-```kotlin
-class MyTest {
- @Test
- fun testMySuspendingFunction() = runBlocking<Unit> {
- // here we can use suspending functions using any assertion style that we like
- }
-}
+```text
+Hello
+World!
```
-<!--- CLEAR -->
+<!--- TEST -->
-## Waiting for a job
+## Scope builder and concurrency
-Delaying for a time while another coroutine is working is not a good approach. Let's explicitly
-wait (in a non-blocking way) until the background [Job] that we have launched is complete:
+A [coroutineScope][_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:
```kotlin
import kotlinx.coroutines.*
-fun main() = runBlocking {
//sampleStart
- val job = GlobalScope.launch { // launch a new coroutine and keep a reference to its Job
+// Sequentially executes doWorld followed by "Done"
+fun main() = runBlocking {
+ doWorld()
+ println("Done")
+}
+
+// Concurrently executes both sections
+suspend fun doWorld() = coroutineScope { // this: CoroutineScope
+ launch {
+ delay(2000L)
+ println("World 2")
+ }
+ launch {
delay(1000L)
- println("World!")
+ println("World 1")
}
- println("Hello,")
- job.join() // wait until child coroutine completes
-//sampleEnd
+ println("Hello")
}
+//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
@@ -158,42 +194,39 @@ fun main() = runBlocking {
>
{type="note"}
-<!--- TEST
-Hello,
-World!
--->
-
-Now the result is still the same, but the code of the main coroutine is not tied to the duration of
-the background job in any way. Much better.
+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][_coroutineScope] in `doWorld` completes only after both are complete, so `doWorld` returns and
+allows `Done` string to be printed only after that:
-## Structured concurrency
+```text
+Hello
+World 1
+World 2
+Done
+```
-There is still something to be desired for practical usage of coroutines.
-When we use `GlobalScope.launch`, we create a top-level coroutine. Even though it is light-weight, it still
-consumes some memory resources while it runs. If we forget to keep a reference to the newly launched
-coroutine, it still runs. What if the code in the coroutine hangs (for example, we erroneously
-delay for too long), what if we launched too many coroutines and ran out of memory?
-Having to manually keep references to all the launched coroutines and [join][Job.join] them is error-prone.
+<!--- TEST -->
-There is a better solution. We can use structured concurrency in our code.
-Instead of launching coroutines in the [GlobalScope], just like we usually do with threads (threads are always global),
-we can launch coroutines in the specific scope of the operation we are performing.
+## An explicit job
-In our example, we have a `main` function that is turned into a coroutine using the [runBlocking] coroutine builder.
-Every coroutine builder, including `runBlocking`, adds an instance of [CoroutineScope] to the scope of its code block.
-We can launch coroutines in this scope without having to `join` them explicitly, because
-an outer coroutine (`runBlocking` in our example) does not complete until all the coroutines launched
-in its scope complete. Thus, we can make our example simpler:
+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:
```kotlin
import kotlinx.coroutines.*
-fun main() = runBlocking { // this: CoroutineScope
- launch { // launch a new coroutine in the scope of runBlocking
+fun main() = runBlocking {
+//sampleStart
+ val job = launch { // launch a new coroutine and keep a reference to its Job
delay(1000L)
println("World!")
}
- println("Hello,")
+ println("Hello")
+ job.join() // wait until child coroutine completes
+ println("Done")
+//sampleEnd
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
@@ -202,101 +235,15 @@ fun main() = runBlocking { // this: CoroutineScope
>
{type="note"}
-<!--- TEST
-Hello,
-World!
--->
-
-## Scope builder
-
-In addition to the coroutine scope provided by different builders, it is possible to declare your own scope using the
-[coroutineScope][_coroutineScope] builder. It creates a coroutine scope and does not complete until all launched children complete.
-
-[runBlocking] and [coroutineScope][_coroutineScope] 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][_coroutineScope] just suspends, releasing the underlying thread for other usages.
-Because of that difference, [runBlocking] is a regular function and [coroutineScope][_coroutineScope] is a suspending function.
-
-It can be demonstrated by the following example:
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking { // this: CoroutineScope
- launch {
- delay(200L)
- println("Task from runBlocking")
- }
-
- coroutineScope { // Creates a coroutine scope
- launch {
- delay(500L)
- println("Task from nested launch")
- }
-
- delay(100L)
- println("Task from coroutine scope") // This line will be printed before the nested launch
- }
-
- println("Coroutine scope is over") // This line is not printed until the nested launch completes
-}
-```
-{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt).
->
-{type="note"}
-
-<!--- TEST
-Task from coroutine scope
-Task from runBlocking
-Task from nested launch
-Coroutine scope is over
--->
+This code produces:
-Note that right after the "Task from coroutine scope" message (while waiting for nested launch)
- "Task from runBlocking" is executed and printed — even though the [coroutineScope][_coroutineScope] is not completed yet.
-
-## 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.
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking {
- launch { doWorld() }
- println("Hello,")
-}
-
-// this is your first suspending function
-suspend fun doWorld() {
- delay(1000L)
- println("World!")
-}
-```
-{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-basic-07.kt).
->
-{type="note"}
-
-<!--- TEST
-Hello,
+```text
+Hello
World!
--->
+Done
+```
-But what if the extracted function contains a coroutine builder which is invoked on the current scope?
-In this case, the `suspend` modifier on the extracted function is not enough. Making `doWorld` an extension
-method on `CoroutineScope` is one of the solutions, but it may not always be applicable as it does not make the API clearer.
-The idiomatic solution is to have either an explicit `CoroutineScope` as a field in a class containing the target function
-or an implicit one when the outer class implements `CoroutineScope`.
-As a last resort, [CoroutineScope(coroutineContext)][CoroutineScope()] can be used, but such an approach is structurally unsafe
-because you no longer have control on the scope of execution of this method. Only private APIs can use this builder.
+<!--- TEST -->
## Coroutines ARE light-weight
@@ -305,6 +252,7 @@ Run the following code:
```kotlin
import kotlinx.coroutines.*
+//sampleStart
fun main() = runBlocking {
repeat(100_000) { // launch a lot of coroutines
launch {
@@ -313,9 +261,10 @@ fun main() = runBlocking {
}
}
}
+//sampleEnd
```
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-basic-08.kt).
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt).
>
{type="note"}
@@ -323,57 +272,17 @@ fun main() = runBlocking {
It launches 100K coroutines and, after 5 seconds, each coroutine prints a dot.
-Now, try that with threads. What would happen? (Most likely your code will produce some sort of out-of-memory error)
-
-## Global coroutines are like daemon threads
-
-The following code launches a long-running coroutine in [GlobalScope] that prints "I'm sleeping" twice a second and then
-returns from the main function after some delay:
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking {
-//sampleStart
- GlobalScope.launch {
- repeat(1000) { i ->
- println("I'm sleeping $i ...")
- delay(500L)
- }
- }
- delay(1300L) // just quit after delay
-//sampleEnd
-}
-```
-{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-basic-09.kt).
->
-{type="note"}
-
-You can run and see that it prints three lines and terminates:
-
-```text
-I'm sleeping 0 ...
-I'm sleeping 1 ...
-I'm sleeping 2 ...
-```
-
-<!--- TEST -->
-
-Active coroutines that were launched in [GlobalScope] do not keep the process alive. They are like daemon threads.
+Now, try that with threads (remove `runBlocking`, replace `launch` with `thread`, and replace `delay` with `Thread.sleep`).
+What would happen? (Most likely your code will produce some sort of out-of-memory error)
<!--- MODULE kotlinx-coroutines-core -->
<!--- INDEX kotlinx.coroutines -->
[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
-[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
-[GlobalScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html
[delay]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
-[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
-[Job.join]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html
+[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
[_coroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html
-[CoroutineScope()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope.html
+[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
<!--- END -->