diff --git a/docs/images/new-mvn-project-jvm.png b/docs/images/new-mvn-project-jvm.png deleted file mode 100644 index 2154a3df3d..0000000000 Binary files a/docs/images/new-mvn-project-jvm.png and /dev/null differ diff --git a/docs/topics/composing-suspending-functions.md b/docs/topics/composing-suspending-functions.md index 0af60d00a9..9c1a26a910 100644 --- a/docs/topics/composing-suspending-functions.md +++ b/docs/topics/composing-suspending-functions.md @@ -24,7 +24,7 @@ suspend fun doSomethingUsefulTwo(): Int { What do we do if we need them to be invoked _sequentially_ — first `doSomethingUsefulOne` _and then_ `doSomethingUsefulTwo`, and compute the sum of their results? -In practice we do this if we use the result of the first function to make a decision on whether we need +In practice, we do this if we use the result of the first function to make a decision on whether we need to invoke the second one or to decide on how to invoke it. We use a normal sequential invocation, because the code in the coroutine, just like in the regular @@ -190,18 +190,26 @@ standard `lazy` function in cases when computation of the value involves suspend ## Async-style functions We can define async-style functions that invoke `doSomethingUsefulOne` and `doSomethingUsefulTwo` -_asynchronously_ using the [async] coroutine builder with an explicit [GlobalScope] reference. +_asynchronously_ using the [async] coroutine builder using a [GlobalScope] reference to +opt-out of the structured concurrency. We name such functions with the "...Async" suffix to highlight the fact that they only start asynchronous computation and one needs to use the resulting deferred value to get the result. +> [GlobalScope] is a delicate API that can backfire in non-trivial ways, one of which will be explained +> below, so you must explicitly opt-in into using `GlobalScope` with `@OptIn(DelicateCoroutinesApi::class)`. +> +{type="note"} + ```kotlin // The result type of somethingUsefulOneAsync is Deferred +@OptIn(DelicateCoroutinesApi::class) fun somethingUsefulOneAsync() = GlobalScope.async { doSomethingUsefulOne() } // The result type of somethingUsefulTwoAsync is Deferred +@OptIn(DelicateCoroutinesApi::class) fun somethingUsefulTwoAsync() = GlobalScope.async { doSomethingUsefulTwo() } @@ -236,10 +244,12 @@ fun main() { } //sampleEnd +@OptIn(DelicateCoroutinesApi::class) fun somethingUsefulOneAsync() = GlobalScope.async { doSomethingUsefulOne() } +@OptIn(DelicateCoroutinesApi::class) fun somethingUsefulTwoAsync() = GlobalScope.async { doSomethingUsefulTwo() } @@ -272,9 +282,9 @@ Completed in 1085 ms {type="note"} Consider what happens if between the `val one = somethingUsefulOneAsync()` line and `one.await()` expression there is some logic -error in the code and the program throws an exception and the operation that was being performed by the program aborts. +error in the code, and the program throws an exception, and the operation that was being performed by the program aborts. Normally, a global error-handler could catch this exception, log and report the error for developers, but the program -could otherwise continue doing other operations. But here we have `somethingUsefulOneAsync` still running in the background, +could otherwise continue doing other operations. However, here we have `somethingUsefulOneAsync` still running in the background, even though the operation that initiated it was aborted. This problem does not happen with structured concurrency, as shown in the section below. @@ -293,7 +303,7 @@ suspend fun concurrentSum(): Int = coroutineScope { } ``` -This way, if something goes wrong inside the code of the `concurrentSum` function and it throws an exception, +This way, if something goes wrong inside the code of the `concurrentSum` function, and it throws an exception, all the coroutines that were launched in its scope will be cancelled. @@ -403,4 +413,4 @@ Computation failed with ArithmeticException [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 - \ No newline at end of file + diff --git a/docs/topics/coroutine-context-and-dispatchers.md b/docs/topics/coroutine-context-and-dispatchers.md index 9aae1a7a4b..402db103b4 100644 --- a/docs/topics/coroutine-context-and-dispatchers.md +++ b/docs/topics/coroutine-context-and-dispatchers.md @@ -65,9 +65,8 @@ context of the main `runBlocking` coroutine which runs in the `main` thread. [Dispatchers.Unconfined] is a special dispatcher that also appears to run in the `main` thread, but it is, in fact, a different mechanism that is explained later. -The default dispatcher that is used when coroutines are launched in [GlobalScope] -is represented by [Dispatchers.Default] and uses a shared background pool of threads, -so `launch(Dispatchers.Default) { ... }` uses the same dispatcher as `GlobalScope.launch { ... }`. +The default dispatcher that is used when no other dispatcher is explicitly specified in the scope. +It is represented by [Dispatchers.Default] and uses a shared background pool of threads. [newSingleThreadContext] creates a thread for the coroutine to run. A dedicated thread is a very expensive resource. @@ -303,8 +302,14 @@ the [Job] of the new coroutine becomes a _child_ of the parent coroutine's job. When the parent coroutine is cancelled, all its children are recursively cancelled, too. -However, when [GlobalScope] is used to launch a coroutine, there is no parent for the job of the new coroutine. -It is therefore not tied to the scope it was launched from and operates independently. +However, this parent-child relation can be explicitly overriden in one of two ways: + +1. When a different scope is explicitly specified when launching a coroutine (for example, `GlobalScope.launch`), + then it does not inherit a `Job` from the parent scope. +2. When a different `Job` object is passed as the context for the new coroutine (as show in the example below), + then it overrides the `Job` of the parent scope. + +In both cases, the launched coroutine is not tied to the scope it was launched from and operates independently. ```kotlin import kotlinx.coroutines.* @@ -313,9 +318,9 @@ fun main() = runBlocking { //sampleStart // launch a coroutine to process some kind of incoming request val request = launch { - // it spawns two other jobs, one with GlobalScope - GlobalScope.launch { - println("job1: I run in GlobalScope and execute independently!") + // it spawns two other jobs + launch(Job()) { + println("job1: I run in my own Job and execute independently!") delay(1000) println("job1: I am not affected by cancellation of the request") } @@ -343,7 +348,7 @@ fun main() = runBlocking { The output of this code is: ```text -job1: I run in GlobalScope and execute independently! +job1: I run in my own Job and execute independently! job2: I am a child of the request coroutine job1: I am not affected by cancellation of the request main: Who has survived request cancellation? @@ -659,7 +664,6 @@ that should be implemented. [async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html [CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html [Dispatchers.Unconfined]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-unconfined.html -[GlobalScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html [Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html [newSingleThreadContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/new-single-thread-context.html [ExecutorCoroutineDispatcher.close]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-executor-coroutine-dispatcher/close.html @@ -678,4 +682,4 @@ that should be implemented. [ensurePresent]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/java.lang.-thread-local/ensure-present.html [ThreadContextElement]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-thread-context-element/index.html - \ No newline at end of file + diff --git a/docs/topics/coroutines-basic-jvm.md b/docs/topics/coroutines-basic-jvm.md deleted file mode 100644 index a6ca3ba5f3..0000000000 --- a/docs/topics/coroutines-basic-jvm.md +++ /dev/null @@ -1,262 +0,0 @@ -[//]: # (title: Create a basic coroutine – tutorial) - -Kotlin 1.1 introduced coroutines, a new way of writing asynchronous, non-blocking code (and much more). In this tutorial you will go through some basics of using Kotlin coroutines with the help of the `kotlinx.coroutines` library, which is a collection of helpers and wrappers for existing Java libraries. - -## Set up a project - -### Gradle - -In IntelliJ IDEA go to **File** \| **New** \| **Project**.: - -![Create a new project](new-gradle-project-jvm.png) - -Then follow the wizard steps. You'll have a `build.gradle` file created with Kotlin configured according to [this document](gradle.md). -Make sure it's configured for Kotlin 1.3 or higher. - -Since we'll be using the [`kotlinx.coroutines`](https://github.com/Kotlin/kotlinx.coroutines), let's add its recent version to our dependencies: - - - -```groovy -dependencies { - ... - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:%coroutinesVersion%' -} -``` - -```kotlin -dependencies { - ... - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:%coroutinesVersion%") -} -``` - - -This library is published to the [Maven Central repository](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core), so add the following: - -```groovy -repositories { - mavenCentral() -} -``` - -That's it, we are good to go and write code under `src/main/kotlin`. - -### Maven - -In IntelliJ IDEA go to **File** \| **New** \| **Project** and check the **Create from archetype** box: - -![Create a new project](new-mvn-project-jvm.png) - -Then follow the wizard steps. You'll have a `pom.xml` file created with Kotlin configured according to [this document](maven.md). -Make sure it's configured for Kotlin 1.3 or higher. - -```xml - - org.jetbrains.kotlin - kotlin-maven-plugin - ... - -``` - -Since we'll be using the [`kotlinx.coroutines`](https://github.com/Kotlin/kotlinx.coroutines), let's add its recent version to our dependencies: - -```xml - - ... - - org.jetbrains.kotlinx - kotlinx-coroutines-core - %coroutinesVersion% - - -``` - -This library is published to the [Maven Central repository](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core), which Maven will download from by default. - -That's it, we are good to go and write code under `src/main/kotlin`. - -## My first coroutine - -One can think of a coroutine as a light-weight thread. Like threads, coroutines can run in parallel, wait for each other and communicate. -The biggest difference is that coroutines are very cheap, almost free: we can create thousands of them, and pay very little in terms of performance. -True threads, on the other hand, are expensive to start and keep around. A thousand threads can be a serious challenge for a modern machine. - -So, how do we start a coroutine? Let's use the `launch {}` function: - -```kotlin -launch { - ... -} -``` - -This starts a new coroutine. By default, coroutines are run on a shared pool of threads. -Threads still exist in a program based on coroutines, but one thread can run many coroutines, so there's no need for -too many threads. - -Let's look at a full program that uses `launch`: - -```kotlin -import kotlinx.coroutines.* - -fun main(args: Array) { -//sampleStart - println("Start") - - // Start a coroutine - GlobalScope.launch { - delay(1000) - println("Hello") - } - - Thread.sleep(2000) // wait for 2 seconds - println("Stop") -//sampleEnd -} -``` -{kotlin-runnable="true" kotlin-min-compiler-version="1.3"} - -Here we start a coroutine that waits for 1 second and prints `Hello`. - -We are using the `delay()` function that's like `Thread.sleep()`, but better: it _doesn't block a thread_, but only suspends the coroutine itself. -The thread is returned to the pool while the coroutine is waiting, and when the waiting is done, the coroutine resumes on a free thread in the pool. - -The main thread (that runs the `main()` function) must wait until our coroutine completes, otherwise the program ends before `Hello` is printed. - -_Exercise: try removing the `sleep()` from the program above and see the result._ - - If we try to use the same non-blocking `delay()` function directly inside `main()`, we'll get a compiler error: - -> Suspend functions are only allowed to be called from a coroutine or another suspend function. -> -{type="note"} - -This is because we are not inside any coroutine. We can use delay if we wrap it into `runBlocking {}` that starts a coroutine and waits until it's done: - -```kotlin -runBlocking { - delay(2000) -} -``` - -So, first the resulting program prints `Start`, then it runs a coroutine through `launch {}`, then it runs another one through `runBlocking {}` and blocks until it's done, then prints `Stop`. Meanwhile the first coroutine completes and prints `Hello`. Just like threads, we told you :) - -## Let's run a lot of them - -Now, let's make sure that coroutines are really cheaper than threads. How about starting a million of them? Let's try starting a million threads first: - -```kotlin -val c = AtomicLong() - -for (i in 1..1_000_000L) - thread(start = true) { - c.addAndGet(i) - } - -println(c.get()) -``` - -This runs a 1'000'000 threads each of which adds to a common counter. My patience runs out before this program completes on my machine (definitely over a minute). - -Let's try the same with coroutines: - -```kotlin -val c = AtomicLong() - -for (i in 1..1_000_000L) - GlobalScope.launch { - c.addAndGet(i) - } - -println(c.get()) -``` - -This example completes in less than a second for me, but it prints some arbitrary number, because some coroutines don't finish before `main()` prints the result. Let's fix that. - -We could use the same means of synchronization that are applicable to threads (a `CountDownLatch` is what crosses my mind in this case), but let's take a safer and cleaner path. - -## Async: returning a value from a coroutine - -Another way of starting a coroutine is `async {}`. It is like `launch {}`, but returns an instance of `Deferred`, which has an `await()` function that returns the result of the coroutine. `Deferred` is a very basic [future](https://en.wikipedia.org/wiki/Futures_and_promises) (fully-fledged JDK futures are also supported, but here we'll confine ourselves to `Deferred` for now). - -Let's create a million coroutines again, keeping their `Deferred` objects. Now there's no need in the atomic counter, as we can just return the numbers to be added from our coroutines: - -```kotlin -val deferred = (1..1_000_000).map { n -> - GlobalScope.async { - n - } -} -``` - -All these have already started, all we need is collect the results: - -```kotlin -val sum = deferred.sumOf { it.await().toLong() } -``` - -We simply take every coroutine and await its result here, then all results are added together by the standard library function `sumOf()`. But the compiler rightfully complains: - -> Suspend functions are only allowed to be called from a coroutine or another suspend function. -> -{type="note"} - -`await()` can not be called outside a coroutine, because it needs to suspend until the computation finishes, and only coroutines can suspend in a non-blocking way. So, let's put this inside a coroutine: - -```kotlin -runBlocking { - val sum = deferred.sumOf { it.await().toLong() } - println("Sum: $sum") -} -``` - -Now it prints something sensible: `500000500000`, because all coroutines complete. - -Let's also make sure that our coroutines actually run in parallel. If we add a 1-second `delay()` to each of the `async`'s, the resulting program won't run for 1'000'000 seconds (over 11,5 days): - -```kotlin -val deferred = (1..1_000_000).map { n -> - GlobalScope.async { - delay(1000) - n - } -} -``` - -This takes about 10 seconds on my machine, so yes, coroutines do run in parallel. - -## Suspending functions - -Now, let's say we want to extract our _workload_ (which is "wait 1 second and return a number") into a separate function: - -```kotlin -fun workload(n: Int): Int { - delay(1000) - return n -} -``` - -A familiar error pops up: - -> Suspend functions are only allowed to be called from a coroutine or another suspend function. -> -{type="note"} - -Let's dig a little into what it means. The biggest merit of coroutines is that they can _suspend_ without blocking a thread. The compiler has to emit some special code to make this possible, so we have to mark functions that _may suspend_ explicitly in the code. We use the `suspend` modifier for it: - -```kotlin -suspend fun workload(n: Int): Int { - delay(1000) - return n -} -``` - -Now when we call `workload()` from a coroutine, the compiler knows that it may suspend and will prepare accordingly: - -```kotlin -GlobalScope.async { - workload(n) -} -``` - -Our `workload()` function can be called from a coroutine (or another suspending function), but _cannot_ be called from outside a coroutine. Naturally, `delay()` and `await()` that we used above are themselves declared as `suspend`, and this is why we had to put them inside `runBlocking {}`, `launch {}` or `async {}`. diff --git a/docs/topics/coroutines-basics.md b/docs/topics/coroutines-basics.md index c1c17581c0..ab8b427685 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! ``` -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. -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`). +[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. -If you start by replacing `GlobalScope.launch` with `thread`, the compiler produces the following error: +[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 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 — 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 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. + +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. -## Bridging blocking and non-blocking worlds +## Extract function refactoring -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: +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"} -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 { // 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,41 @@ fun main() = runBlocking { // start main coroutine > {type="note"} +This code also prints: + -Here `runBlocking { ... }` 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 is also a way to write unit tests for suspending functions: - - - -```kotlin -class MyTest { - @Test - fun testMySuspendingFunction() = runBlocking { - // here we can use suspending functions using any assertion style that we like - } -} -``` - - +## Scope builder and concurrency -## Waiting for a job - -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 "Hello" +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 +192,39 @@ fun main() = runBlocking { > {type="note"} - - -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. + -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 +233,15 @@ fun main() = runBlocking { // this: CoroutineScope > {type="note"} - - -## 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"} - - - -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"} +This code produces: - +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. + ## Coroutines ARE light-weight @@ -305,6 +250,7 @@ Run the following code: ```kotlin import kotlinx.coroutines.* +//sampleStart fun main() = runBlocking { repeat(100_000) { // launch a lot of coroutines launch { @@ -313,9 +259,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 +270,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 ... -``` - - - -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) [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 diff --git a/docs/topics/coroutines-guide.md b/docs/topics/coroutines-guide.md index 3e9fa90490..050376ce1b 100644 --- a/docs/topics/coroutines-guide.md +++ b/docs/topics/coroutines-guide.md @@ -17,7 +17,6 @@ In order to use coroutines as well as follow the examples in this guide, you nee ## Table of contents * [Coroutines basics](coroutines-basics.md) -* [Tutorial: Create a basic coroutine](coroutines-basic-jvm.md) * [Hands-on: Intro to coroutines and channels](https://play.kotlinlang.org/hands-on/Introduction%20to%20Coroutines%20and%20Channels) * [Cancellation and timeouts](cancellation-and-timeouts.md) * [Composing suspending functions](composing-suspending-functions.md) diff --git a/docs/topics/exception-handling.md b/docs/topics/exception-handling.md index fbd11313cc..4cff42f357 100644 --- a/docs/topics/exception-handling.md +++ b/docs/topics/exception-handling.md @@ -19,9 +19,16 @@ exception, for example via [await][Deferred.await] or [receive][ReceiveChannel.r It can be demonstrated by a simple example that creates root coroutines using the [GlobalScope]: +> [GlobalScope] is a delicate API that can backfire in non-trivial ways. Creating a root coroutine for the +> whole application is one of the rare legitimate uses for `GlobalScope`, so you must explicitly opt-in into +> using `GlobalScope` with `@OptIn(DelicateCoroutinesApi::class)`. +> +{type="note"} + ```kotlin import kotlinx.coroutines.* +@OptIn(DelicateCoroutinesApi::class) fun main() = runBlocking { val job = GlobalScope.launch { // root coroutine with launch println("Throwing exception from launch") @@ -90,6 +97,7 @@ so its `CoroutineExceptionHandler` has no effect either. ```kotlin import kotlinx.coroutines.* +@OptIn(DelicateCoroutinesApi::class) fun main() = runBlocking { //sampleStart val handler = CoroutineExceptionHandler { _, exception -> @@ -184,6 +192,7 @@ which is demonstrated by the following example. ```kotlin import kotlinx.coroutines.* +@OptIn(DelicateCoroutinesApi::class) fun main() = runBlocking { //sampleStart val handler = CoroutineExceptionHandler { _, exception -> @@ -242,6 +251,7 @@ import kotlinx.coroutines.exceptions.* import kotlinx.coroutines.* import java.io.* +@OptIn(DelicateCoroutinesApi::class) fun main() = runBlocking { val handler = CoroutineExceptionHandler { _, exception -> println("CoroutineExceptionHandler got $exception with suppressed ${exception.suppressed.contentToString()}") @@ -292,6 +302,7 @@ Cancellation exceptions are transparent and are unwrapped by default: import kotlinx.coroutines.* import java.io.* +@OptIn(DelicateCoroutinesApi::class) fun main() = runBlocking { //sampleStart val handler = CoroutineExceptionHandler { _, exception -> @@ -510,4 +521,4 @@ The scope is completed [produce]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html [ReceiveChannel.receive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive.html - \ No newline at end of file + diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index cf9906fc5a..716b8cddbc 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -270,6 +270,9 @@ public final class kotlinx/coroutines/DelayKt { public static final fun delay-VtjQ1oo (DLkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public abstract interface annotation class kotlinx/coroutines/DelicateCoroutinesApi : java/lang/annotation/Annotation { +} + public final class kotlinx/coroutines/Dispatchers { public static final field INSTANCE Lkotlinx/coroutines/Dispatchers; public static final fun getDefault ()Lkotlinx/coroutines/CoroutineDispatcher; diff --git a/kotlinx-coroutines-core/common/src/Annotations.kt b/kotlinx-coroutines-core/common/src/Annotations.kt index 70adad9b97..ac5396d49a 100644 --- a/kotlinx-coroutines-core/common/src/Annotations.kt +++ b/kotlinx-coroutines-core/common/src/Annotations.kt @@ -6,6 +6,22 @@ package kotlinx.coroutines import kotlinx.coroutines.flow.* +/** + * Marks declarations in the coroutines that are **delicate** — + * they have limited use-case and shall be used with care in general code. + * Any use of a delicate declaration has to be carefully reviewed to make sure it is + * properly used and does not create problems like memory and resource leaks. + * Carefully read documentation of any declaration marked as `DelicateCoroutinesApi`. + */ +@MustBeDocumented +@Retention(value = AnnotationRetention.BINARY) +@RequiresOptIn( + level = RequiresOptIn.Level.WARNING, + message = "This is a delicate API and its use requires care." + + " Make sure you fully read and understand documentation of the declaration that is marked as a delicate API." +) +public annotation class DelicateCoroutinesApi + /** * Marks declarations that are still **experimental** in coroutines API, which means that the design of the * corresponding declarations has open issues which may (or may not) lead to their changes in the future. diff --git a/kotlinx-coroutines-core/common/src/CoroutineScope.kt b/kotlinx-coroutines-core/common/src/CoroutineScope.kt index e7c243a42d..df2ee615dc 100644 --- a/kotlinx-coroutines-core/common/src/CoroutineScope.kt +++ b/kotlinx-coroutines-core/common/src/CoroutineScope.kt @@ -16,7 +16,10 @@ import kotlin.coroutines.intrinsics.* * is an extension on [CoroutineScope] and inherits its [coroutineContext][CoroutineScope.coroutineContext] * to automatically propagate all its elements and cancellation. * - * The best ways to obtain a standalone instance of the scope are [CoroutineScope()] and [MainScope()] factory functions. + * The best ways to obtain a standalone instance of the scope are [CoroutineScope()] and [MainScope()] factory functions, + * taking care to cancel these coroutine scopes when they are no longer needed (see section on custom usage below for + * explanation and example). + * * Additional context elements can be appended to the scope using the [plus][CoroutineScope.plus] operator. * * ### Convention for structured concurrency @@ -38,12 +41,23 @@ import kotlin.coroutines.intrinsics.* * * ### Custom usage * - * [CoroutineScope] should be implemented or declared as a property on entities with a well-defined lifecycle that are - * responsible for launching children coroutines, for example: + * `CoroutineScope` should be declared as a property on entities with a well-defined lifecycle that are + * responsible for launching children coroutines. The corresponding instance of `CoroutineScope` shall be created + * with either `CoroutineScope()` or `MainScope()` functions. The difference between them is only in the + * [CoroutineDispatcher]: + * + * * `CoroutineScope()` uses [Dispatchers.Default] for its coroutines. + * * `MainScope()` uses [Dispatchers.Main] for its coroutines. + * + * **The key part of custom usage of `CustomScope` is cancelling it and the end of the lifecycle.** + * The [CoroutineScope.cancel] extension function shall be used when the entity that was launching coroutines + * is no longer needed. It cancels all the coroutines that might still be running on behalf of it. + * + * For example: * * ``` * class MyUIClass { - * val scope = MainScope() // the scope of MyUIClass + * val scope = MainScope() // the scope of MyUIClass, uses Dispatchers.Main * * fun destroy() { // destroys an instance of MyUIClass * scope.cancel() // cancels all coroutines launched in this scope @@ -124,25 +138,81 @@ public val CoroutineScope.isActive: Boolean /** * A global [CoroutineScope] not bound to any job. - * * Global scope is used to launch top-level coroutines which are operating on the whole application lifetime * and are not cancelled prematurely. - * Another use of the global scope is operators running in [Dispatchers.Unconfined], which don't have any job associated with them. * - * Application code usually should use an application-defined [CoroutineScope]. Using - * [async][CoroutineScope.async] or [launch][CoroutineScope.launch] - * on the instance of [GlobalScope] is highly discouraged. + * Active coroutines launched in `GlobalScope` do not keep the process alive. They are like daemon threads. + * + * This is a **delicate** API. It is easy to accidentally create resource or memory leaks when + * `GlobalScope` is used. A coroutine launched in `GlobalScope` is not subject to the principle of structured + * concurrency, so if it hangs or gets delayed due to a problem (e.g. due to a slow network), it will stay working + * and consuming resources. For example, consider the following code: + * + * ``` + * fun loadConfiguration() { + * GlobalScope.launch { + * val config = fetchConfigFromServer() // network request + * updateConfiguration(config) + * } + * } + * ``` + * + * A call to `loadConfiguration` creates a coroutine in the `GlobalScope` that works in background without any + * provision to cancel it or to wait for its completion. If a network is slow, it keeps waiting in background, + * consuming resources. Repeated calls to `loadConfiguration` will consume more and more resources. + * + * ### Possible replacements + * + * In may cases uses of `GlobalScope` should be removed, marking the containing operation with `suspend`, for example: + * + * ``` + * suspend fun loadConfiguration() { + * val config = fetchConfigFromServer() // network request + * updateConfiguration(config) + * } + * ``` + * + * In cases when `GlobalScope.launch` was used to launch multiple concurrent operations, the corresponding + * operations shall be grouped with [coroutineScope] instead: + * + * ``` + * // concurrently load configuration and data + * suspend fun loadConfigurationAndData() { + * coroutinesScope { + * launch { loadConfiguration() } + * launch { loadData() } + * } + * } + * ``` + * + * In top-level code, when launching a concurrent operation operation from a non-suspending context, an appropriately + * confined instance of [CoroutineScope] shall be used instead of a `GlobalScope`. See docs on [CoroutineScope] for + * details. + * + * ### GlobalScope vs custom scope + * + * Do not replace `GlobalScope.launch { ... }` with `CoroutineScope().launch { ... }` constructor function call. + * The latter has the same pitfalls as `GlobalScope`. See [CoroutineScope] documentation on the intended usage of + * `CoroutineScope()` constructor function. + * + * ### Legitimate use-cases * - * Usage of this interface may look like this: + * There are limited circumstances under which `GlobalScope` can be legitimately and safely used, such as top-level background + * processes that must stay active for the whole duration of the application's lifetime. Because of that, any use + * of `GlobalScope` requires an explicit opt-in with `@OptIn(DelicateCoroutinesApi::class)`, like this: * * ``` - * fun ReceiveChannel.sqrt(): ReceiveChannel = GlobalScope.produce(Dispatchers.Unconfined) { - * for (number in this) { - * send(Math.sqrt(number)) + * // A global coroutine to log statistics every second, must be always active + * @OptIn(DelicateCoroutinesApi::class) + * val globalScopeReporter = GlobalScope.launch { + * while (true) { + * delay(1000) + * logStatistics() * } * } * ``` */ +@DelicateCoroutinesApi public object GlobalScope : CoroutineScope { /** * Returns [EmptyCoroutineContext]. diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-01.kt index f04b100a8a..529f881730 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-01.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-01.kt @@ -7,11 +7,10 @@ package kotlinx.coroutines.guide.exampleBasic01 import kotlinx.coroutines.* -fun main() { - GlobalScope.launch { // launch a new coroutine in background and continue +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 } diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-02.kt index bfece26bc9..6bf2af4c31 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-02.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-02.kt @@ -7,13 +7,13 @@ package kotlinx.coroutines.guide.exampleBasic02 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 - } +fun main() = runBlocking { // this: CoroutineScope + launch { doWorld() } + println("Hello") +} + +// this is your first suspending function +suspend fun doWorld() { + delay(1000L) + println("World!") } diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-03.kt index 8541f60472..67b6894ada 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-03.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-03.kt @@ -7,11 +7,14 @@ package kotlinx.coroutines.guide.exampleBasic03 import kotlinx.coroutines.* -fun main() = runBlocking { // start main coroutine - GlobalScope.launch { // launch a new coroutine in background and continue +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") } diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-04.kt index 69f827715d..3531b22083 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-04.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-04.kt @@ -7,11 +7,21 @@ package kotlinx.coroutines.guide.exampleBasic04 import kotlinx.coroutines.* +// Sequentially executes doWorld followed by "Hello" fun main() = runBlocking { - val job = GlobalScope.launch { // launch a new coroutine and keep a reference to its Job + 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 + println("Hello") } diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-05.kt index 9d530b5f2b..193f2cc3aa 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-05.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-05.kt @@ -7,10 +7,12 @@ package kotlinx.coroutines.guide.exampleBasic05 import kotlinx.coroutines.* -fun main() = runBlocking { // this: CoroutineScope - launch { // launch a new coroutine in the scope of runBlocking +fun main() = runBlocking { + 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") } diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt index b53d3b8962..24b890a0ad 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt @@ -7,21 +7,11 @@ package kotlinx.coroutines.guide.exampleBasic06 import kotlinx.coroutines.* -fun main() = runBlocking { // this: CoroutineScope - launch { - delay(200L) - println("Task from runBlocking") - } - - coroutineScope { // Creates a coroutine scope +fun main() = runBlocking { + repeat(100_000) { // launch a lot of coroutines launch { - delay(500L) - println("Task from nested launch") + delay(5000L) + print(".") } - - 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 } diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-07.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-07.kt deleted file mode 100644 index cd854ce89a..0000000000 --- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-07.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -// This file was automatically generated from coroutines-basics.md by Knit tool. Do not edit. -package kotlinx.coroutines.guide.exampleBasic07 - -import kotlinx.coroutines.* - -fun main() = runBlocking { - launch { doWorld() } - println("Hello,") -} - -// this is your first suspending function -suspend fun doWorld() { - delay(1000L) - println("World!") -} diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-08.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-08.kt deleted file mode 100644 index 0a346e0be8..0000000000 --- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-08.kt +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -// This file was automatically generated from coroutines-basics.md by Knit tool. Do not edit. -package kotlinx.coroutines.guide.exampleBasic08 - -import kotlinx.coroutines.* - -fun main() = runBlocking { - repeat(100_000) { // launch a lot of coroutines - launch { - delay(5000L) - print(".") - } - } -} diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-09.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-09.kt deleted file mode 100644 index c9783ee50f..0000000000 --- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-09.kt +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -// This file was automatically generated from coroutines-basics.md by Knit tool. Do not edit. -package kotlinx.coroutines.guide.exampleBasic09 - -import kotlinx.coroutines.* - -fun main() = runBlocking { - GlobalScope.launch { - repeat(1000) { i -> - println("I'm sleeping $i ...") - delay(500L) - } - } - delay(1300L) // just quit after delay -} diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-compose-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-compose-04.kt index 312dc72b55..35536a7d14 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/example-compose-04.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-compose-04.kt @@ -23,10 +23,12 @@ fun main() { println("Completed in $time ms") } +@OptIn(DelicateCoroutinesApi::class) fun somethingUsefulOneAsync() = GlobalScope.async { doSomethingUsefulOne() } +@OptIn(DelicateCoroutinesApi::class) fun somethingUsefulTwoAsync() = GlobalScope.async { doSomethingUsefulTwo() } diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt index e23eaf2542..c6ad4516da 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt @@ -10,9 +10,9 @@ import kotlinx.coroutines.* fun main() = runBlocking { // launch a coroutine to process some kind of incoming request val request = launch { - // it spawns two other jobs, one with GlobalScope - GlobalScope.launch { - println("job1: I run in GlobalScope and execute independently!") + // it spawns two other jobs + launch(Job()) { + println("job1: I run in my own Job and execute independently!") delay(1000) println("job1: I am not affected by cancellation of the request") } diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-01.kt index e08ddd0811..24cbabe094 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-01.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-01.kt @@ -7,6 +7,7 @@ package kotlinx.coroutines.guide.exampleExceptions01 import kotlinx.coroutines.* +@OptIn(DelicateCoroutinesApi::class) fun main() = runBlocking { val job = GlobalScope.launch { // root coroutine with launch println("Throwing exception from launch") diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-02.kt index 67fdaa7177..c3ab68a53a 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-02.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-02.kt @@ -7,6 +7,7 @@ package kotlinx.coroutines.guide.exampleExceptions02 import kotlinx.coroutines.* +@OptIn(DelicateCoroutinesApi::class) fun main() = runBlocking { val handler = CoroutineExceptionHandler { _, exception -> println("CoroutineExceptionHandler got $exception") diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-04.kt index 9c9b43d22e..b966c1eab4 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-04.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-04.kt @@ -7,6 +7,7 @@ package kotlinx.coroutines.guide.exampleExceptions04 import kotlinx.coroutines.* +@OptIn(DelicateCoroutinesApi::class) fun main() = runBlocking { val handler = CoroutineExceptionHandler { _, exception -> println("CoroutineExceptionHandler got $exception") diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-05.kt index 04f9385f06..5f1f3d89a9 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-05.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-05.kt @@ -10,6 +10,7 @@ import kotlinx.coroutines.exceptions.* import kotlinx.coroutines.* import java.io.* +@OptIn(DelicateCoroutinesApi::class) fun main() = runBlocking { val handler = CoroutineExceptionHandler { _, exception -> println("CoroutineExceptionHandler got $exception with suppressed ${exception.suppressed.contentToString()}") diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-06.kt index 5a5b276bc3..bc9f77b936 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-06.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-06.kt @@ -8,6 +8,7 @@ package kotlinx.coroutines.guide.exampleExceptions06 import kotlinx.coroutines.* import java.io.* +@OptIn(DelicateCoroutinesApi::class) fun main() = runBlocking { val handler = CoroutineExceptionHandler { _, exception -> println("CoroutineExceptionHandler got $exception") diff --git a/kotlinx-coroutines-core/jvm/test/guide/test/BasicsGuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/BasicsGuideTest.kt index 765cd0b9df..7e54fb1d26 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/test/BasicsGuideTest.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/test/BasicsGuideTest.kt @@ -12,7 +12,7 @@ class BasicsGuideTest { @Test fun testExampleBasic01() { test("ExampleBasic01") { kotlinx.coroutines.guide.exampleBasic01.main() }.verifyLines( - "Hello,", + "Hello", "World!" ) } @@ -20,7 +20,7 @@ class BasicsGuideTest { @Test fun testExampleBasic02() { test("ExampleBasic02") { kotlinx.coroutines.guide.exampleBasic02.main() }.verifyLines( - "Hello,", + "Hello", "World!" ) } @@ -28,7 +28,7 @@ class BasicsGuideTest { @Test fun testExampleBasic03() { test("ExampleBasic03") { kotlinx.coroutines.guide.exampleBasic03.main() }.verifyLines( - "Hello,", + "Hello", "World!" ) } @@ -36,50 +36,26 @@ class BasicsGuideTest { @Test fun testExampleBasic04() { test("ExampleBasic04") { kotlinx.coroutines.guide.exampleBasic04.main() }.verifyLines( - "Hello,", - "World!" + "Hello", + "World 1", + "World 2", + "Done" ) } @Test fun testExampleBasic05() { test("ExampleBasic05") { kotlinx.coroutines.guide.exampleBasic05.main() }.verifyLines( - "Hello,", - "World!" + "Hello", + "World!", + "Done" ) } @Test fun testExampleBasic06() { - test("ExampleBasic06") { kotlinx.coroutines.guide.exampleBasic06.main() }.verifyLines( - "Task from coroutine scope", - "Task from runBlocking", - "Task from nested launch", - "Coroutine scope is over" - ) - } - - @Test - fun testExampleBasic07() { - test("ExampleBasic07") { kotlinx.coroutines.guide.exampleBasic07.main() }.verifyLines( - "Hello,", - "World!" - ) - } - - @Test - fun testExampleBasic08() { - test("ExampleBasic08") { kotlinx.coroutines.guide.exampleBasic08.main() }.also { lines -> + test("ExampleBasic06") { kotlinx.coroutines.guide.exampleBasic06.main() }.also { lines -> check(lines.size == 1 && lines[0] == ".".repeat(100_000)) } } - - @Test - fun testExampleBasic09() { - test("ExampleBasic09") { kotlinx.coroutines.guide.exampleBasic09.main() }.verifyLines( - "I'm sleeping 0 ...", - "I'm sleeping 1 ...", - "I'm sleeping 2 ..." - ) - } } diff --git a/kotlinx-coroutines-core/jvm/test/guide/test/DispatcherGuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/DispatcherGuideTest.kt index d6f1c21dc0..1a84fb9427 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/test/DispatcherGuideTest.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/test/DispatcherGuideTest.kt @@ -57,7 +57,7 @@ class DispatcherGuideTest { @Test fun testExampleContext06() { test("ExampleContext06") { kotlinx.coroutines.guide.exampleContext06.main() }.verifyLines( - "job1: I run in GlobalScope and execute independently!", + "job1: I run in my own Job and execute independently!", "job2: I am a child of the request coroutine", "job1: I am not affected by cancellation of the request", "main: Who has survived request cancellation?"