From 500521eb91f615ffb231aa7f5246ca05d51b7fa2 Mon Sep 17 00:00:00 2001 From: Daniel Csorba Date: Tue, 8 Jul 2025 14:40:55 +0200 Subject: [PATCH 1/5] update: rewriting coroutines basics for coroutines guide updates --- docs/images/run-icon.png | Bin 0 -> 1145 bytes docs/topics/coroutines-basics.md | 515 +++++++++++++++++++++---------- 2 files changed, 346 insertions(+), 169 deletions(-) create mode 100644 docs/images/run-icon.png diff --git a/docs/images/run-icon.png b/docs/images/run-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ebc6d3e91cfc017cd975408259c9f3d7ef7de9ae GIT binary patch literal 1145 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-#^NA%Cx&(BWL^R}6{!)PX}-P; zT0k}j0}G=R11m@g5Zf_I!`W_(8Vt-}aV7?a_DlvAs2V>Y4FVH@7^DY6GcRC-ss@TJ zV1lbQTEL88gS0Jo69>7Jv%n*=n1Mk|6@(c*gH%2+Ffh;Zba4#HV0{}=?Gcf$|dbT+i#kfI++9=In;G3 zV_K(I+bk8)%xRMpXXbCuyq|a0{@9(=^4oJy{bk$zz3zF9_5S;o&u^EXd+Ix>(bC}A zSw=n)*1i)y-74aOdP-HF7^X*673n;lKi62$FzpS~rh*8gu3P8adc0LX6pI|1qkrMq z^DR-QXPlV6&+o`R|HI$yZABQejkTBLS~souy3pX!M)?f6%Te0PzNlnbNj=z;BFds7 z!C6Px@ z`Zw?MEZDH^vWT#9?J<|IxQ`2Y%;v9}Wu!CFGaw|s?KZEfgp_>aN{S&z-$#)wT0gs*4=HuXxLv*YSQq^#}2u1I{aM`_1|2+p>0PuaoQR z)qcU*Rwlb+m;xLx=>|%$?%T+DDtdlGyN1-wt*uXZq~_<%bo;zUV4||oD!YAGIJRh|h6Q;~eRynL+NS!vg=@bXtBP4wC^Wy3f4+2e z=*r9IHi`+Rs>FSJr_p!P$t^)kY-aSsx7*d;=ye@G`YPvyn);Nhne%zwT>57dCOO zd=&Or`|+#rWbZ67o*sV1<=^DG?uQ0NC9ZSK+7jB$b{lbCE< zbir6=d2Qb+x7BIa5?o6Dem`wBQU0%ep|8%N$7P03yNK~Qtye5 z6rRejsv0>OhgX#FOj{ypy-kyiQ+jcN(aG ztY2v=MTb{7C**kDm44J+*q*_$;Z@GL6-IHg0alXkE$5tbPVem4@V3yZI>zON&5><$ kx;bvkE#G|H6)g3iN&kocm7^ymr9oNP)78&qol`;+0QuL}aR2}S literal 0 HcmV?d00001 diff --git a/docs/topics/coroutines-basics.md b/docs/topics/coroutines-basics.md index 5b467c58bf..950dd058d0 100644 --- a/docs/topics/coroutines-basics.md +++ b/docs/topics/coroutines-basics.md @@ -3,212 +3,326 @@ [//]: # (title: Coroutines basics) -This section covers basic coroutine concepts. +To create applications that perform multiple tasks at once, a concept known as concurrency, +Kotlin uses coroutines. Coroutines let you write concurrent code in a clear and sequential style. -## Your first coroutine +The most basic building block of coroutines is the suspending function. +It makes it possible to pause a running operation and resume it later without losing the structure of your code. -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. +To mark a function as suspendable, use the `suspend` keyword: -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. +```kotlin +suspend fun greet() { + println("Hello world from a suspending function") +} +``` -Run the following code to get to your first working coroutine: +You can only call a suspending function from another suspending function or from a coroutine. +Even the `main()` function can be suspending: ```kotlin -import kotlinx.coroutines.* +suspend fun main() { + showUserInfo() +} -//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 coroutine continues while a previous one is delayed +suspend fun showUserInfo() { + println("Loading user...") + greet() + println("User: John Smith") +} + +suspend fun greet() { + println("Hello world from a suspending function") } -//sampleEnd ``` -{kotlin-runnable="true" kotlin-min-compiler-version="1.3"} - -> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-basic-01.kt). -> -{style="note"} +{kotlin-runnable="true"} -You will see the following result: +This example doesn’t use concurrency yet, but by marking the functions with the `suspend` keyword, +you allow them to be used in concurrent code later. -```text -Hello -World! -``` +While the `suspend` keyword is part of the Kotlin standard library, most coroutine features +are available through the [`kotlinx.coroutines`](https://github.com/Kotlin/kotlinx.coroutines) library. - +## Add the kotlinx.coroutines library to your project -Let's dissect what this code does. +To include the `kotlinx.coroutines` library in your project, add the corresponding dependency configuration based on your build tool. -[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. +### Gradle -[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. +Add the following dependency to your `build.gradle(.kts)` file: -[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]: +```kotlin +// build.gradle.kts +repositories { + mavenCentral() +} +dependencies { + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:%coroutinesVersion%") +} ``` -Unresolved reference: launch + + + + +```groovy +// build.gradle +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:%coroutinesVersion%' +} ``` + + + +### Maven + +Add the following dependency to your `pom.xml` file. + +```xml + + + + org.jetbrains.kotlin + kotlinx-coroutines-core + %coroutinesVersion% + + + ... + +``` + +## Create your first coroutines + +Suspending functions are the basic building blocks for concurrency in Kotlin, but they can only run inside another suspending function or a coroutine. +A coroutine is a suspendable computation that runs on _threads_ and can run concurrently or in parallel with other coroutines. + +On the JVM, all concurrent code, such as coroutines, runs on threads, which are managed by the operating system. +Coroutines can suspend their execution instead of blocking a thread, which lets you run many tasks with less overhead. -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. +> You can display coroutine names next to thread names in the output of your code for additional information. +> In IntelliJ IDEA, right-click the `Run` icon: ![Run icon](run-icon.png){width=30} next to your `main()` function, select `Modify Run Configuration...`, and add `-Dkotlinx.coroutines.debug` in `VM options`. +> +> See [Debug coroutines using IntelliJ IDEA — Tutorial](debug-coroutines-with-idea.md) for more information. +> +{style="tip"} -### Structured concurrency +To create a coroutine in Kotlin, you need a suspending function, a scope, a builder to start it, and a dispatcher to control which threads it uses: -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. +1. Import the `kotlinx.coroutines` library: -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. + ```kotlin + import kotlinx.coroutines.* + ``` -## Extract function refactoring +2. Mark functions that can pause and resume with the `suspend` keyword: -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 + suspend fun greet() { + println("Hello world from a suspending function on thread: ${Thread.currentThread().name}") + } + + suspend fun main() {} + ``` + + > While you can mark the `main()` function as `suspend` in some projects, other projects or frameworks might not allow changing the `main()` function this way. + > In those cases, coroutine builder functions like [`runBlocking`](#runblocking) are the usual entry point for calling suspending code. + > + {style="note"} + + +3. Use the [`coroutineScope()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html) function to [create a `CoroutineScope`](#coroutine-scope-and-structured-concurrency) that groups your coroutines and controls their lifecycle: + + ```kotlin + suspend fun main() = coroutineScope { + // Add coroutine builder here + } + ``` + +4. Use a [coroutine builder function](#coroutine-builder-functions) like [`.launch()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html) to start the coroutine: + + ```kotlin + suspend fun main() = coroutineScope { + // Starts a coroutine inside the scope + launch { + greet() + } + println("This runs while the coroutine is active") + } + ``` + +5. Specify a [coroutine dispatcher](coroutine-context-and-dispatchers.md) like [`Dispatchers.Default`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html) to control the [thread pool](#comparing-coroutines-and-jvm-threads) for your coroutine: + + ```kotlin + suspend fun main() = coroutineScope { + // Starts a coroutine inside the scope with a specified dispatcher + launch(Dispatchers.Default) { + greet() + } + println("This runs while the coroutine is active") + } + ``` + + > If you don’t specify a coroutine dispatcher, a coroutine builder like `.launch()` inherits the dispatcher from its `CoroutineScope`. + > If the scope doesn’t define a dispatcher, the builder uses `Dispatchers.Default`. + > + {style="note"} + +6. Combine these pieces to run multiple coroutines at the same time on a shared pool of threads: + + ```kotlin + // Imports the coroutines library + import kotlinx.coroutines.* + + // Defines a suspending function + suspend fun greet() { + println("Hello from greet() on thread: ${Thread.currentThread().name}") + } + + suspend fun main() = coroutineScope { + // Starts a coroutine with Dispatchers.Default + launch(Dispatchers.Default) { + greet() + } + + // Starts another coroutine + launch(Dispatchers.Default) { + println("Second coroutine on thread: ${Thread.currentThread().name}") + } + + // Runs code in the coroutine scope + println("This runs while the coroutine is active") + } + ``` + {kotlin-runnable="true"} + +This example demonstrates simple multithreading with coroutines on a shared thread pool. + +> Try running the example multiple times. +> You may notice that the output order and thread names may change each time you run the program. +> This is because the JVM and OS decide when threads run and tasks complete. +> +{style="tip"} + +## Coroutine scope and structured concurrency + +Coroutines follow a principle of _structured concurrency_, which means new coroutines can only be launched in a specific [`CoroutineScope`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/) that defines their lifecycle. + +When you work with multiple coroutines, you often need to run many tasks at once, cancel them when they are no longer needed, and handle errors safely. +Without clear structure, you risk leaving coroutines running longer than needed, wasting resources. + +Structured concurrency keeps coroutines organized in a hierarchy. +When you launch a coroutine, it belongs to its scope. +If the scope is canceled, all its child coroutines are canceled too. + +[Coroutine builder functions](#coroutine-builder-functions) are extension functions on `CoroutineScope`. +When you start a coroutine inside another coroutine, it automatically becomes a child of its parent scope. +The parent coroutine's scope waits for all its children to finish before it completes. + +To create a coroutine scope without launching a new coroutine, use the [`coroutineScope()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html) function: ```kotlin import kotlinx.coroutines.* -//sampleStart -fun main() = runBlocking { // this: CoroutineScope - launch { doWorld() } - println("Hello") -} +suspend fun main() = coroutineScope { + // Starts a new coroutine in the coroutine scope + launch { + // Suspends for one second without blocking the thread + delay(1000L) + println("Child coroutine completed") + } + launch { + delay(2000L) + println("Another child coroutine completed") + } -// this is your first suspending function -suspend fun doWorld() { - delay(1000L) - println("World!") + // Runs immediately in the parent coroutine + println("Coroutine scope completed") } -//sampleEnd ``` -{kotlin-runnable="true" kotlin-min-compiler-version="1.3"} - -> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-basic-02.kt). -> -{style="note"} +{kotlin-runnable="true"} - +This example uses the [`delay()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html#) function to show how the coroutine scope waits for its child coroutines to finish. +You can specify the wait time in milliseconds, so `delay(1000L)` suspends the coroutine for one second without blocking the thread. -## Scope builder +## Coroutine builder functions -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. +Coroutine builder functions create new coroutines inside an existing [coroutine scope](#coroutine-scope-and-structured-concurrency). +Each builder defines how the coroutine starts and how you interact with its result. -[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. +### .launch() -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: +The [`.launch()`()](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html#) coroutine builder function starts a new coroutine without blocking the rest of the scope. +Use `.launch()` to run a task alongside other work when the result isn't needed or you don't want to wait for it: ```kotlin import kotlinx.coroutines.* -//sampleStart -fun main() = runBlocking { - doWorld() -} - -suspend fun doWorld() = coroutineScope { // this: CoroutineScope +suspend fun main() = coroutineScope { + // Starts a coroutine that runs without blocking the scope launch { - delay(1000L) - println("World!") + println("Sending notification in background") } - println("Hello") + + // Main coroutine continues while a previous one is delayed + println("Scope continues") } -//sampleEnd ``` -{kotlin-runnable="true" kotlin-min-compiler-version="1.3"} - -> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-basic-03.kt). -> -{style="note"} +{kotlin-runnable="true"} + +After running this example, you can see that the `main()` function isn’t blocked by `.launch()` and keeps running other code while the coroutine works in the background. + +### .async() + +Use the [`.async()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html) builder function to start an asynchronous computation that runs alongside other code and returns a result you can access later. +The result is wrapped in a `Deferred` object, which you can access by calling the `.await()` function: + +```kotlin +import kotlinx.coroutines.* -This code also prints: +suspend fun main() = coroutineScope { + // Starts a coroutine that returns a number + val result = async { + 2 + 3 + } -```text -Hello -World! + // Gets the result from the coroutine with the await() function + println("The result is ${result.await()}") +} ``` +{kotlin-runnable="true"} - +### .runBlocking() -## Scope builder and concurrency +Use the [`.runBlocking()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html) coroutine builder function to create a coroutine scope in a blocking context. +Instead of using a shared thread pool, `.runBlocking()` blocks the [thread](#comparing-coroutines-and-jvm-threads) it runs on and waits for all coroutines inside it to finish before continuing. -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: +`.runBlocking()` creates a new `CoroutineScope` for the code inside it. +This lets you call suspending functions without marking the surrounding function as `suspend`, for example, in the `main()` function: ```kotlin import kotlinx.coroutines.* -//sampleStart -// Sequentially executes doWorld followed by "Done" +// Runs the greet() function inside a blocking scope fun main() = runBlocking { - doWorld() + greet() + delay(1000L) println("Done") } -// Concurrently executes both sections -suspend fun doWorld() = coroutineScope { // this: CoroutineScope - launch { - delay(2000L) - println("World 2") - } - launch { - delay(1000L) - println("World 1") - } - println("Hello") +suspend fun greet() { + println("Hello from a suspending function") } -//sampleEnd -``` -{kotlin-runnable="true" kotlin-min-compiler-version="1.3"} - -> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-basic-04.kt). -> -{style="note"} - -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: - -```text -Hello -World 1 -World 2 -Done ``` +{kotlin-runnable="true"} - - + -> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-basic-05.kt). +--> + +## Coroutine dispatchers + +A [coroutine dispatcher](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/#) controls which thread or thread pool coroutines use for their execution. +Coroutines are not tied to a single thread. +They can pause on one thread and resume on another, depending on the dispatcher. +This lets you run many coroutines at the same time without blocking threads. + +A dispatcher works together with the [coroutine scope](#coroutine-scope-and-structured-concurrency) to define when coroutines run and where they run. +While the coroutine scope controls the coroutine’s lifecycle, the dispatcher controls which threads are used for execution. + +> You don't have to create a dispatcher for every coroutine. +> By default, coroutines inherit the dispatcher from their parent scope. +> You can specify a dispatcher if you need to run a coroutine in a different context. > {style="note"} -This code produces: +The `kotlinx.coroutines` library includes different dispatchers for different use cases. +For example, [`Dispatchers.Default`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html) runs coroutines on a shared pool of threads performing work in the background, +separate from the main thread. +This multithreaded dispatcher is optimized for CPU-intensive tasks like calculations or data processing: + +```kotlin +import kotlinx.coroutines.* + +fun main() = runBlocking { + // Runs runBlocking on the main thread + println("runBlocking running on ${Thread.currentThread().name}") + + val one = async(Dispatchers.Default) { + // Starts first calculation on a thread in the Default dispatcher’s thread pool + println("First calculation starting on ${Thread.currentThread().name}") + val sum = (1..500_000).sum() + // Suspends for 200 ms + delay(200L) + println("First calculation done on ${Thread.currentThread().name}") + sum + } + + val two = async(Dispatchers.Default) { + // Starts second calculation on a thread in the Default dispatcher’s thread pool + println("Second calculation starting on ${Thread.currentThread().name}") + val sum = (500_001..1_000_000).sum() + println("Second calculation done on ${Thread.currentThread().name}") + sum + } -```text -Hello -World! -Done + // Waits for both calculations and prints the result + println("Combined total: ${one.await() + two.await()}") +} ``` +{kotlin-runnable="true"} + +For more information, see [Coroutine context and dispatchers](coroutine-context-and-dispatchers.md) + +## Comparing coroutines and JVM threads - +While coroutines are suspendable computations that run code concurrently like threads on the JVM, they work differently under the hood. -## Coroutines are light-weight +A _thread_ is managed by the operating system. Threads can run tasks parallel on multiple CPU cores and represent a standard approach to concurrency on the JVM. +When you create a thread, the operating system allocates memory for its stack and uses the kernel to switch between threads. +This makes threads powerful but also resource-intensive. +Each thread usually needs a few megabytes of memory, and typically the JVM can only handle a few thousand threads at once. -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: +On the other hand, a coroutine isn't bound to a specific thread. +It can suspend on one thread and resume on another, so many coroutines can share the same thread pool. +When a coroutine suspends, the thread isn't blocked, and it remains free to run other tasks. +This makes coroutines much lighter than threads and allows running hundreds of thousands in one process without exhausting system resources. + +Let’s look at an example where 50,000 coroutines each wait five seconds and then print a period (`.`): ```kotlin import kotlinx.coroutines.* @@ -268,26 +431,40 @@ fun main() = runBlocking { ``` {kotlin-runnable="true" kotlin-min-compiler-version="1.3"} -> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt). + -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. +Now let's look at the same example using JVM threads: + +```kotlin +import kotlin.concurrent.thread + +fun main() { + repeat(50_000) { + thread { + Thread.sleep(5000L) + print(".") + } + } +} +``` + +Running this version uses much more memory because each thread needs its own memory stack. +Depending on your operating system, JDK version, and settings, +it may either throw an out-of-memory error or slow down thread creation to avoid running too many threads at once. -[launch]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html -[delay]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html -[runBlocking]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html -[CoroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html -[_coroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html -[Job]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html +## What's next + +* Discover more about combining suspending functions in [Composing suspending functions](composing-suspending-functions.md). +* Learn how to cancel coroutines and handle timeouts in [Cancellation and timeouts](cancellation-and-timeouts.md). +* Dive deeper into coroutine execution and thread management in [Coroutine context and dispatchers](coroutine-context-and-dispatchers.md). +* Learn how to return multiple asynchronously computed values in [Asynchronous flows](flow.md) From e925f9732428752a9fe2e285bf45a77664e89c6a Mon Sep 17 00:00:00 2001 From: Daniel Csorba Date: Thu, 24 Jul 2025 16:22:48 +0200 Subject: [PATCH 2/5] update: implementing comments from first review round --- docs/images/coroutines-and-threads.svg | 27 ++ docs/images/parallelism-and-concurrency.svg | 42 +++ docs/topics/coroutines-basics.md | 355 +++++++++++--------- 3 files changed, 261 insertions(+), 163 deletions(-) create mode 100644 docs/images/coroutines-and-threads.svg create mode 100644 docs/images/parallelism-and-concurrency.svg diff --git a/docs/images/coroutines-and-threads.svg b/docs/images/coroutines-and-threads.svg new file mode 100644 index 0000000000..ed38d9d52b --- /dev/null +++ b/docs/images/coroutines-and-threads.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/images/parallelism-and-concurrency.svg b/docs/images/parallelism-and-concurrency.svg new file mode 100644 index 0000000000..6e56f96ede --- /dev/null +++ b/docs/images/parallelism-and-concurrency.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/topics/coroutines-basics.md b/docs/topics/coroutines-basics.md index 950dd058d0..ff92c0f472 100644 --- a/docs/topics/coroutines-basics.md +++ b/docs/topics/coroutines-basics.md @@ -1,4 +1,3 @@ - https://github.com/Kotlin/kotlinx.coroutines/edit/master/docs/topics/ [//]: # (title: Coroutines basics) @@ -17,8 +16,8 @@ suspend fun greet() { } ``` -You can only call a suspending function from another suspending function or from a coroutine. -Even the `main()` function can be suspending: +You can only call a suspending function from another suspending function. +To call suspending functions at the entry point of a Kotlin application, mark the `main()` function as suspendable: ```kotlin suspend fun main() { @@ -37,10 +36,10 @@ suspend fun greet() { ``` {kotlin-runnable="true"} -This example doesn’t use concurrency yet, but by marking the functions with the `suspend` keyword, -you allow them to be used in concurrent code later. +This example doesn't use concurrency yet, but by marking the functions with the `suspend` keyword, +you allow them to call other suspending functions and run concurrent code inside. -While the `suspend` keyword is part of the Kotlin standard library, most coroutine features +While the `suspend` keyword is part of the core Kotlin language, most coroutine features are available through the [`kotlinx.coroutines`](https://github.com/Kotlin/kotlinx.coroutines) library. ## Add the kotlinx.coroutines library to your project @@ -100,20 +99,30 @@ Add the following dependency to your `pom.xml` file. ## Create your first coroutines -Suspending functions are the basic building blocks for concurrency in Kotlin, but they can only run inside another suspending function or a coroutine. -A coroutine is a suspendable computation that runs on _threads_ and can run concurrently or in parallel with other coroutines. +Suspending functions are the basic building blocks for concurrency in Kotlin. +A coroutine is a suspendable computation that can run concurrently with other coroutines and potentially in parallel. -On the JVM, all concurrent code, such as coroutines, runs on threads, which are managed by the operating system. -Coroutines can suspend their execution instead of blocking a thread, which lets you run many tasks with less overhead. +On the JVM and in Kotlin/Native, all concurrent code, such as coroutines, runs on _threads_, which are managed by the operating system. +Coroutines can suspend their execution instead of blocking a thread. +This allows one coroutine to suspend while waiting for a network response and another to run on the same thread, ensuring effective resource utilization. + +![Comparing parallel and concurrent threads](parallelism-and-concurrency.svg){width="700"} + +To create a coroutine in Kotlin, you need the following: + +* A suspending function. +* A coroutine scope in which it can run, such as one available inside the `withContext()` function. +* A coroutine builder like `.launch()` to start it. +* A dispatcher to control which threads it uses. > You can display coroutine names next to thread names in the output of your code for additional information. -> In IntelliJ IDEA, right-click the `Run` icon: ![Run icon](run-icon.png){width=30} next to your `main()` function, select `Modify Run Configuration...`, and add `-Dkotlinx.coroutines.debug` in `VM options`. -> -> See [Debug coroutines using IntelliJ IDEA — Tutorial](debug-coroutines-with-idea.md) for more information. -> -{style="tip"} +> To do so, pass the `-Dkotlinx.coroutines.debug` VM option in your build tool or IDE run configuration. +> +> See [Debugging coroutines](https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/topics/debugging.md) for more information. +> +{style="tip"} -To create a coroutine in Kotlin, you need a suspending function, a scope, a builder to start it, and a dispatcher to control which threads it uses: +Let's look at an example that uses multiple coroutines in a multithreaded environment: 1. Import the `kotlinx.coroutines` library: @@ -131,50 +140,40 @@ To create a coroutine in Kotlin, you need a suspending function, a scope, a buil suspend fun main() {} ``` - > While you can mark the `main()` function as `suspend` in some projects, other projects or frameworks might not allow changing the `main()` function this way. - > In those cases, coroutine builder functions like [`runBlocking`](#runblocking) are the usual entry point for calling suspending code. + > While you can mark the `main()` function as `suspend` in some projects, it may not be possible when integrating with existing code or using a framework. + > In that case, check the framework’s documentation to see if it supports calling suspending functions. + > If not, use [`runBlocking`](#runblocking) to call them by blocking the current thread. > {style="note"} -3. Use the [`coroutineScope()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html) function to [create a `CoroutineScope`](#coroutine-scope-and-structured-concurrency) that groups your coroutines and controls their lifecycle: +3. Use [`withContext(Dispatchers.Default)`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html#) to define a safe entry point for multithreaded concurrent code that runs on a shared thread pool: ```kotlin - suspend fun main() = coroutineScope { - // Add coroutine builder here + suspend fun main() = withContext(Dispatchers.Default) { + // Add the coroutine builders here } ``` + > The `withContext()` function is typically used for [context switching](coroutine-context-and-dispatchers.md#jumping-between-threads), but in this example, + > it also defines a safe entry point for concurrent code. + > It uses the [`Dispatchers.Default` dispatcher](#coroutine-dispatchers) to run code on a shared thread pool for multithreaded execution. + > + > The coroutines launched inside the `withContext()` block share the same coroutine scope, which ensures [structured concurrency](#coroutine-scope-and-structured-concurrency). + > + {style="note"} + 4. Use a [coroutine builder function](#coroutine-builder-functions) like [`.launch()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html) to start the coroutine: ```kotlin - suspend fun main() = coroutineScope { + suspend fun main() = withContext(Dispatchers.Default) { // Starts a coroutine inside the scope - launch { - greet() - } - println("This runs while the coroutine is active") + launch { greet() } + println("This runs concurrently and possibly in parallel with the launched coroutines on thread: ${Thread.currentThread().name}") } ``` -5. Specify a [coroutine dispatcher](coroutine-context-and-dispatchers.md) like [`Dispatchers.Default`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html) to control the [thread pool](#comparing-coroutines-and-jvm-threads) for your coroutine: - - ```kotlin - suspend fun main() = coroutineScope { - // Starts a coroutine inside the scope with a specified dispatcher - launch(Dispatchers.Default) { - greet() - } - println("This runs while the coroutine is active") - } - ``` - - > If you don’t specify a coroutine dispatcher, a coroutine builder like `.launch()` inherits the dispatcher from its `CoroutineScope`. - > If the scope doesn’t define a dispatcher, the builder uses `Dispatchers.Default`. - > - {style="note"} - -6. Combine these pieces to run multiple coroutines at the same time on a shared pool of threads: +5. Combine these pieces to run multiple coroutines at the same time on a shared pool of threads: ```kotlin // Imports the coroutines library @@ -182,22 +181,21 @@ To create a coroutine in Kotlin, you need a suspending function, a scope, a buil // Defines a suspending function suspend fun greet() { - println("Hello from greet() on thread: ${Thread.currentThread().name}") + println("Hello from greet() on thread: ${Thread.currentThread().name}") } - suspend fun main() = coroutineScope { - // Starts a coroutine with Dispatchers.Default - launch(Dispatchers.Default) { - greet() - } + // Runs all concurrent code on a shared thread pool + suspend fun main() = withContext(Dispatchers.Default) { + launch() { + greet() + } // Starts another coroutine - launch(Dispatchers.Default) { - println("Second coroutine on thread: ${Thread.currentThread().name}") + launch() { + println("Another coroutine on thread: ${Thread.currentThread().name}") } - // Runs code in the coroutine scope - println("This runs while the coroutine is active") + println("This runs concurrently and possibly in parallel with the launched coroutines on thread: ${Thread.currentThread().name}") } ``` {kotlin-runnable="true"} @@ -206,20 +204,25 @@ This example demonstrates simple multithreading with coroutines on a shared thre > Try running the example multiple times. > You may notice that the output order and thread names may change each time you run the program. -> This is because the JVM and OS decide when threads run and tasks complete. +> This is because the OS decides when threads run and tasks complete. > {style="tip"} ## Coroutine scope and structured concurrency -Coroutines follow a principle of _structured concurrency_, which means new coroutines can only be launched in a specific [`CoroutineScope`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/) that defines their lifecycle. +When you run many coroutines in an application, you need a way to manage them as a group. +Kotlin coroutines rely on a principle called _structured concurrency_ to provide this structure. -When you work with multiple coroutines, you often need to run many tasks at once, cancel them when they are no longer needed, and handle errors safely. -Without clear structure, you risk leaving coroutines running longer than needed, wasting resources. +This principle connects coroutines into a hierarchy of parent and child tasks that share the same lifecycle. +A parent coroutine waits for its children to complete before finishing. +If the parent coroutine fails or is canceled, all its child coroutines are canceled too. +Keeping coroutines connected this way makes cancellation, error handling, and resource cleanup predictable and safe. -Structured concurrency keeps coroutines organized in a hierarchy. -When you launch a coroutine, it belongs to its scope. -If the scope is canceled, all its child coroutines are canceled too. +> A coroutine’s lifecycle is the period from its start until it completes, fails, or is canceled. +> +{style="tip"} + +To maintain structured concurrency, new coroutines can only be launched in a [`CoroutineScope`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/) that defines and manages their lifecycle. [Coroutine builder functions](#coroutine-builder-functions) are extension functions on `CoroutineScope`. When you start a coroutine inside another coroutine, it automatically becomes a child of its parent scope. @@ -228,21 +231,25 @@ The parent coroutine's scope waits for all its children to finish before it comp To create a coroutine scope without launching a new coroutine, use the [`coroutineScope()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html) function: ```kotlin +// Imports the kotlin.time.Duration to express duration in seconds +import kotlin.time.Duration.Companion.seconds + import kotlinx.coroutines.* -suspend fun main() = coroutineScope { - // Starts a new coroutine in the coroutine scope - launch { - // Suspends for one second without blocking the thread - delay(1000L) - println("Child coroutine completed") - } - launch { - delay(2000L) - println("Another child coroutine completed") +suspend fun main() { + println("Starting coroutine scope") + coroutineScope { + launch { + delay(1.seconds) + println("The first coroutine completed") + } + launch { + delay(2.seconds) + println("The second coroutine completed") + } } - // Runs immediately in the parent coroutine + // Runs only after all children in the coroutineScope have completed println("Coroutine scope completed") } ``` @@ -251,22 +258,37 @@ suspend fun main() = coroutineScope { This example uses the [`delay()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html#) function to show how the coroutine scope waits for its child coroutines to finish. You can specify the wait time in milliseconds, so `delay(1000L)` suspends the coroutine for one second without blocking the thread. +> Use [`kotlin.time.Duration`](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.time/-duration/) from the Kotlin standard library to express durations like `delay(1.seconds)` instead of using milliseconds. +> +{style="tip"} + ## Coroutine builder functions Coroutine builder functions create new coroutines inside an existing [coroutine scope](#coroutine-scope-and-structured-concurrency). +They require a `CoroutineScope` to run in, either one that is already available, +or one you create using helper functions such as `coroutineScope()` or [`runBlocking()`](#runblocking). Each builder defines how the coroutine starts and how you interact with its result. +> The `withContext()` function doesn't create a new scope but provides access to the current one. +> +{style="tip"} + ### .launch() -The [`.launch()`()](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html#) coroutine builder function starts a new coroutine without blocking the rest of the scope. +The [`.launch()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html#) coroutine builder function starts a new coroutine without blocking the rest of the scope. Use `.launch()` to run a task alongside other work when the result isn't needed or you don't want to wait for it: ```kotlin +// Imports the kotlin.time.Duration to enable expressing duration in milliseconds +import kotlin.time.Duration.Companion.milliseconds + import kotlinx.coroutines.* suspend fun main() = coroutineScope { // Starts a coroutine that runs without blocking the scope launch { + // Delays to simulate background work + delay(100.milliseconds) println("Sending notification in background") } @@ -276,88 +298,84 @@ suspend fun main() = coroutineScope { ``` {kotlin-runnable="true"} -After running this example, you can see that the `main()` function isn’t blocked by `.launch()` and keeps running other code while the coroutine works in the background. +After running this example, you can see that the `main()` function isn't blocked by `.launch()` and keeps running other code while the coroutine works in the background. ### .async() Use the [`.async()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html) builder function to start an asynchronous computation that runs alongside other code and returns a result you can access later. -The result is wrapped in a `Deferred` object, which you can access by calling the `.await()` function: +The result is wrapped in a [`Deferred`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/) object, which you can access by calling the `.await()` function: ```kotlin -import kotlinx.coroutines.* +// Imports the kotlin.time.Duration to enable expressing duration in milliseconds +import kotlin.time.Duration.Companion.milliseconds -suspend fun main() = coroutineScope { - // Starts a coroutine that returns a number - val result = async { - 2 + 3 - } +import kotlinx.coroutines.* - // Gets the result from the coroutine with the await() function - println("The result is ${result.await()}") +suspend fun main() = withContext(Dispatchers.Default) { + // Starts downloading the first page + val firstPage = async { + delay(50.milliseconds) + "First page" + } + + // Starts downloading the second page in parallel + val secondPage = async { + delay(100.milliseconds) + "Second page" + } + + // Awaits both results and compares them + val pagesAreEqual = firstPage.await() == secondPage.await() + println("Pages are equal: $pagesAreEqual") } ``` {kotlin-runnable="true"} -### .runBlocking() +### runBlocking() -Use the [`.runBlocking()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html) coroutine builder function to create a coroutine scope in a blocking context. -Instead of using a shared thread pool, `.runBlocking()` blocks the [thread](#comparing-coroutines-and-jvm-threads) it runs on and waits for all coroutines inside it to finish before continuing. +The [`runBlocking()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html) coroutine builder function creates coroutine scope and blocks the current [thread](#comparing-coroutines-and-jvm-threads) until +the coroutines launched in that scope finish. +Unlike other coroutine builders, `runBlocking()` doesn't use a shared thread pool. -`.runBlocking()` creates a new `CoroutineScope` for the code inside it. -This lets you call suspending functions without marking the surrounding function as `suspend`, for example, in the `main()` function: +Use `runBlocking()` only when there is no other option to call suspending code from non-suspending code: ```kotlin +// Imports the kotlin.time.Duration to enable expressing duration in milliseconds +import kotlin.time.Duration.Companion.milliseconds + import kotlinx.coroutines.* -// Runs the greet() function inside a blocking scope -fun main() = runBlocking { - greet() - delay(1000L) - println("Done") +// A third-party interface we cannot change +interface Repository { + fun readItem(): Int } -suspend fun greet() { - println("Hello from a suspending function") +object MyRepository : Repository { + override fun readItem(): Int { + // Bridges to a suspending function safely + return runBlocking { + myReadItem() + } + } } -``` -{kotlin-runnable="true"} - - ## Coroutine dispatchers A [coroutine dispatcher](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/#) controls which thread or thread pool coroutines use for their execution. -Coroutines are not tied to a single thread. +Coroutines aren't always tied to a single thread. They can pause on one thread and resume on another, depending on the dispatcher. -This lets you run many coroutines at the same time without blocking threads. +This lets you run many coroutines at the same time without allocating a separate thread for every coroutine. A dispatcher works together with the [coroutine scope](#coroutine-scope-and-structured-concurrency) to define when coroutines run and where they run. -While the coroutine scope controls the coroutine’s lifecycle, the dispatcher controls which threads are used for execution. +While the coroutine scope controls the coroutine's lifecycle, the dispatcher controls which threads are used for execution. -> You don't have to create a dispatcher for every coroutine. +> You don't have to specify a dispatcher for every coroutine. > By default, coroutines inherit the dispatcher from their parent scope. > You can specify a dispatcher if you need to run a coroutine in a different context. > @@ -366,39 +384,56 @@ While the coroutine scope controls the coroutine’s lifecycle, the dispatcher c The `kotlinx.coroutines` library includes different dispatchers for different use cases. For example, [`Dispatchers.Default`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html) runs coroutines on a shared pool of threads performing work in the background, separate from the main thread. -This multithreaded dispatcher is optimized for CPU-intensive tasks like calculations or data processing: -```kotlin -import kotlinx.coroutines.* +To specify a dispatcher for a coroutine builder like `.launch()`, pass it as an argument: -fun main() = runBlocking { - // Runs runBlocking on the main thread - println("runBlocking running on ${Thread.currentThread().name}") - - val one = async(Dispatchers.Default) { - // Starts first calculation on a thread in the Default dispatcher’s thread pool - println("First calculation starting on ${Thread.currentThread().name}") - val sum = (1..500_000).sum() - // Suspends for 200 ms - delay(200L) - println("First calculation done on ${Thread.currentThread().name}") - sum +```kotlin +suspend fun runWithDispatcher() = coroutineScope { + launch(Dispatchers.Default) { + println("Running on ${Thread.currentThread().name}") } +} +``` - val two = async(Dispatchers.Default) { - // Starts second calculation on a thread in the Default dispatcher’s thread pool - println("Second calculation starting on ${Thread.currentThread().name}") - val sum = (500_001..1_000_000).sum() - println("Second calculation done on ${Thread.currentThread().name}") - sum - } +Alternatively, you can wrap multiple coroutines in a `withContext()` block to apply a dispatcher to all of them. + +The following example runs on `Dispatchers.Default`, which is optimized for CPU-intensive operations like data processing: - // Waits for both calculations and prints the result - println("Combined total: ${one.await() + two.await()}") +```kotlin +// Imports the kotlin.time.Duration to enable expressing duration in milliseconds +import kotlin.time.Duration.Companion.milliseconds + +import kotlinx.coroutines.* + +suspend fun main() = withContext(Dispatchers.Default) { + println("Running withContext block on ${Thread.currentThread().name}") + + val one = async { + println("First calculation starting on ${Thread.currentThread().name}") + val sum = (1..500_000).sum() + delay(200L) + println("First calculation done on ${Thread.currentThread().name}") + sum + } + + val two = async { + println("Second calculation starting on ${Thread.currentThread().name}") + val sum = (500_001..1_000_000).sum() + println("Second calculation done on ${Thread.currentThread().name}") + sum + } + + // Waits for both calculations and prints the result + println("Combined total: ${one.await() + two.await()}") } ``` {kotlin-runnable="true"} +> Even though coroutines can suspend and resume on different threads, +> values written before the coroutine suspends are still available within the same coroutine when it resumes. +> +{style="tip"} + For more information, see [Coroutine context and dispatchers](coroutine-context-and-dispatchers.md) ## Comparing coroutines and JVM threads @@ -415,28 +450,27 @@ It can suspend on one thread and resume on another, so many coroutines can share When a coroutine suspends, the thread isn't blocked, and it remains free to run other tasks. This makes coroutines much lighter than threads and allows running hundreds of thousands in one process without exhausting system resources. -Let’s look at an example where 50,000 coroutines each wait five seconds and then print a period (`.`): +![Comparing coroutines and threads](coroutines-and-threads.svg){width="700"} + +Let's look at an example where 50,000 coroutines each wait five seconds and then print a period (`.`): ```kotlin +// Imports the kotlin.time.Duration to express duration in seconds +import kotlin.time.Duration.Companion.seconds + import kotlinx.coroutines.* -fun main() = runBlocking { - repeat(50_000) { // launch a lot of coroutines +suspend fun main() = coroutineScope { + // Launches 50,000 coroutines that each wait five seconds, then print a period + repeat(50_000) { launch { - delay(5000L) + delay(5.seconds) print(".") } } } ``` {kotlin-runnable="true" kotlin-min-compiler-version="1.3"} - - - - Now let's look at the same example using JVM threads: @@ -457,14 +491,9 @@ Running this version uses much more memory because each thread needs its own mem Depending on your operating system, JDK version, and settings, it may either throw an out-of-memory error or slow down thread creation to avoid running too many threads at once. - - - ## What's next * Discover more about combining suspending functions in [Composing suspending functions](composing-suspending-functions.md). * Learn how to cancel coroutines and handle timeouts in [Cancellation and timeouts](cancellation-and-timeouts.md). * Dive deeper into coroutine execution and thread management in [Coroutine context and dispatchers](coroutine-context-and-dispatchers.md). * Learn how to return multiple asynchronously computed values in [Asynchronous flows](flow.md) - - From 8d736e402e2935a7fdad1cadca0d9eebed5e0b4a Mon Sep 17 00:00:00 2001 From: Daniel Csorba Date: Wed, 30 Jul 2025 16:50:51 +0200 Subject: [PATCH 3/5] update: adding information on extracting coroutine builders from scope --- docs/topics/coroutines-basics.md | 41 ++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/docs/topics/coroutines-basics.md b/docs/topics/coroutines-basics.md index ff92c0f472..d892b8d090 100644 --- a/docs/topics/coroutines-basics.md +++ b/docs/topics/coroutines-basics.md @@ -8,7 +8,7 @@ Kotlin uses coroutines. Coroutines let you write concurrent code in a clear and The most basic building block of coroutines is the suspending function. It makes it possible to pause a running operation and resume it later without losing the structure of your code. -To mark a function as suspendable, use the `suspend` keyword: +To mark a function as a suspending function, use the `suspend` keyword: ```kotlin suspend fun greet() { @@ -17,7 +17,7 @@ suspend fun greet() { ``` You can only call a suspending function from another suspending function. -To call suspending functions at the entry point of a Kotlin application, mark the `main()` function as suspendable: +To call suspending functions at the entry point of a Kotlin application, mark the `main()` function with the `suspend` keyword: ```kotlin suspend fun main() { @@ -262,6 +262,43 @@ You can specify the wait time in milliseconds, so `delay(1000L)` suspends the co > {style="tip"} +### Extract coroutine builders from the coroutine scope + +The `coroutineScope()` function takes a lambda with a `CoroutineScope` receiver. +Inside this lambda, you can call coroutine builder functions like `.launch()` and `.async()` because they are extension +functions on `CoroutineScope`. + +```kotlin +suspend fun main() = coroutineScope { + launch { println("1") } + launch { println("2") } +} +``` + +To extract the coroutine builders into another function, that function must declare a `CoroutineScope` receiver: + +```kotlin +suspend fun main() = coroutineScope { + // Calls this.launchAll() on the CoroutineScope receiver + launchAll() +} + +// Doesn't declare CoroutineScope as the receiver +suspend fun launchAll() { + // Error: 'launch' is unresolved + launch { println("1") } + launch { println("2") } +} + +// Declares CoroutineScope as the receiver +suspend fun CoroutineScope.launchAll() { + launch { println("1") } + launch { println("2") } +} +``` + +For more information on how lambdas with receivers work in Kotlin, see [Function literals with receiver](lambdas.md#function-literals-with-receiver). + ## Coroutine builder functions Coroutine builder functions create new coroutines inside an existing [coroutine scope](#coroutine-scope-and-structured-concurrency). From f2cc038bb824bf31388a1b8519c25d68cfa1c835 Mon Sep 17 00:00:00 2001 From: Daniel Csorba Date: Wed, 30 Jul 2025 17:13:42 +0200 Subject: [PATCH 4/5] fix: fix indentation in new code example --- docs/topics/coroutines-basics.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/topics/coroutines-basics.md b/docs/topics/coroutines-basics.md index d892b8d090..e8d19c9569 100644 --- a/docs/topics/coroutines-basics.md +++ b/docs/topics/coroutines-basics.md @@ -285,15 +285,15 @@ suspend fun main() = coroutineScope { // Doesn't declare CoroutineScope as the receiver suspend fun launchAll() { - // Error: 'launch' is unresolved + // Error: launch is unresolved launch { println("1") } launch { println("2") } } // Declares CoroutineScope as the receiver suspend fun CoroutineScope.launchAll() { - launch { println("1") } - launch { println("2") } + launch { println("1") } + launch { println("2") } } ``` From a8be1cc2ac253bfb718b206f62c79c63f0e7eb1a Mon Sep 17 00:00:00 2001 From: Daniel Csorba Date: Mon, 4 Aug 2025 15:36:11 +0200 Subject: [PATCH 5/5] implementing comments from Dmitry --- docs/topics/coroutines-basics.md | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/docs/topics/coroutines-basics.md b/docs/topics/coroutines-basics.md index e8d19c9569..56ac6c7225 100644 --- a/docs/topics/coroutines-basics.md +++ b/docs/topics/coroutines-basics.md @@ -88,7 +88,7 @@ Add the following dependency to your `pom.xml` file. - org.jetbrains.kotlin + org.jetbrains.kotlinx kotlinx-coroutines-core %coroutinesVersion% @@ -104,7 +104,7 @@ A coroutine is a suspendable computation that can run concurrently with other co On the JVM and in Kotlin/Native, all concurrent code, such as coroutines, runs on _threads_, which are managed by the operating system. Coroutines can suspend their execution instead of blocking a thread. -This allows one coroutine to suspend while waiting for a network response and another to run on the same thread, ensuring effective resource utilization. +This allows one coroutine to suspend while waiting for some data to arrive and another to run on the same thread, ensuring effective resource utilization. ![Comparing parallel and concurrent threads](parallelism-and-concurrency.svg){width="700"} @@ -213,7 +213,7 @@ This example demonstrates simple multithreading with coroutines on a shared thre When you run many coroutines in an application, you need a way to manage them as a group. Kotlin coroutines rely on a principle called _structured concurrency_ to provide this structure. -This principle connects coroutines into a hierarchy of parent and child tasks that share the same lifecycle. +This principle groups coroutines into a hierarchy of parent and child tasks with linked lifecycles. A parent coroutine waits for its children to complete before finishing. If the parent coroutine fails or is canceled, all its child coroutines are canceled too. Keeping coroutines connected this way makes cancellation, error handling, and resource cleanup predictable and safe. @@ -223,8 +223,6 @@ Keeping coroutines connected this way makes cancellation, error handling, and re {style="tip"} To maintain structured concurrency, new coroutines can only be launched in a [`CoroutineScope`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/) that defines and manages their lifecycle. - -[Coroutine builder functions](#coroutine-builder-functions) are extension functions on `CoroutineScope`. When you start a coroutine inside another coroutine, it automatically becomes a child of its parent scope. The parent coroutine's scope waits for all its children to finish before it completes. @@ -301,18 +299,17 @@ For more information on how lambdas with receivers work in Kotlin, see [Function ## Coroutine builder functions +A coroutine builder function is a function that accepts a `suspend` [lambda](lambdas.md) that defines a coroutine to run. + Coroutine builder functions create new coroutines inside an existing [coroutine scope](#coroutine-scope-and-structured-concurrency). They require a `CoroutineScope` to run in, either one that is already available, -or one you create using helper functions such as `coroutineScope()` or [`runBlocking()`](#runblocking). +or one you create using helper functions such as `coroutineScope()`, [`runBlocking()`](#runblocking), or [`withContext()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html#). Each builder defines how the coroutine starts and how you interact with its result. -> The `withContext()` function doesn't create a new scope but provides access to the current one. -> -{style="tip"} - ### .launch() -The [`.launch()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html#) coroutine builder function starts a new coroutine without blocking the rest of the scope. +The [`.launch()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html#) coroutine builder function is an extension function on `CoroutineScope`. +It starts a new coroutine without blocking the rest of the scope. Use `.launch()` to run a task alongside other work when the result isn't needed or you don't want to wait for it: ```kotlin @@ -339,7 +336,8 @@ After running this example, you can see that the `main()` function isn't blocked ### .async() -Use the [`.async()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html) builder function to start an asynchronous computation that runs alongside other code and returns a result you can access later. +The [`.async()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html) coroutine builder function is an extension function on `CoroutineScope`. +Use it to start an asynchronous computation that runs alongside other code and returns a result you can access later. The result is wrapped in a [`Deferred`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/) object, which you can access by calling the `.await()` function: ```kotlin @@ -372,7 +370,6 @@ suspend fun main() = withContext(Dispatchers.Default) { The [`runBlocking()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html) coroutine builder function creates coroutine scope and blocks the current [thread](#comparing-coroutines-and-jvm-threads) until the coroutines launched in that scope finish. -Unlike other coroutine builders, `runBlocking()` doesn't use a shared thread pool. Use `runBlocking()` only when there is no other option to call suspending code from non-suspending code: @@ -467,7 +464,7 @@ suspend fun main() = withContext(Dispatchers.Default) { {kotlin-runnable="true"} > Even though coroutines can suspend and resume on different threads, -> values written before the coroutine suspends are still available within the same coroutine when it resumes. +> values written before the coroutine suspends are still guaranteed to be available within the same coroutine when it resumes. > {style="tip"}