Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minor Improvements to the documentation and code comments #4346

Open
wants to merge 38 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
191bedc
docs(coroutines-basics): Improve clarity and grammar
jamhour1g Feb 8, 2025
941e007
docs(Yield.kt): Fix verb forms
jamhour1g Feb 8, 2025
43db7c7
docs(cancellation-and-timeouts): Improve punctuation
jamhour1g Feb 8, 2025
ce692bd
docs(cancellation-and-timeouts): Clarify cancellable computation appr…
jamhour1g Feb 8, 2025
3c558fc
docs(cancellation-and-timeouts): Add a direct link to the `use` funct…
jamhour1g Feb 8, 2025
f0b4040
docs(cancellation-and-timeouts): Correct grammar in compound adjectives
jamhour1g Feb 8, 2025
8ea553b
docs(cancellation-and-timeouts): Add reference to TimeoutCancellation…
jamhour1g Feb 8, 2025
2654c94
docs(composing-suspending-functions): Enhance note visibility with st…
jamhour1g Feb 8, 2025
674ce13
docs(composing-suspending-functions): Clarify concurrent function ext…
jamhour1g Feb 8, 2025
3ad9810
docs(coroutine-context-and-dispatchers): Improve explanation for cont…
jamhour1g Feb 8, 2025
ee6b672
docs(Job): Improve readability and formatting
jamhour1g Feb 9, 2025
b73a1d9
docs(CompletableJob.kt): Improve clarity and grammar
jamhour1g Feb 9, 2025
1fabd23
docs(SupervisorJob()): Add a missing article.
jamhour1g Feb 9, 2025
b732faf
docs(coroutine-context-and-dispatchers): Remove an unnecessary article.
jamhour1g Feb 9, 2025
c8bdcae
docs(coroutine-context-and-dispatchers): Fix a typo and remove redund…
jamhour1g Feb 9, 2025
4a08df9
docs(coroutine-context-and-dispatchers): Add missing article
jamhour1g Feb 9, 2025
4ea9dab
docs(coroutine-context-and-dispatchers): Improve readability and clarity
jamhour1g Feb 9, 2025
f62f85a
docs(coroutine-context-and-dispatchers): Corrected minor grammatical …
jamhour1g Feb 9, 2025
984e8ce
docs(coroutine-context-and-dispatchers): Replace the incorrect call t…
jamhour1g Feb 9, 2025
f17954a
docs(CoroutineScope.kt): Add missing articles and link to CoroutineSc…
jamhour1g Feb 9, 2025
4249c7f
docs(ThreadContextElement.kt): Add missing articles
jamhour1g Feb 9, 2025
f08f0ff
docs(ThreadContextElement.kt): Add a missing article and adjust wordi…
jamhour1g Feb 9, 2025
2420621
feat: Add the updated files generated by the Knit tool.
jamhour1g Feb 9, 2025
1800912
docs(cancellation-and-timeouts): Fix inconsistent use of references
jamhour1g Feb 11, 2025
be00959
docs(composing-suspending-functions): Reverse the note changes
jamhour1g Feb 11, 2025
f0988e1
docs(coroutine-context-and-dispatchers): Clarify withContext behavior
jamhour1g Feb 12, 2025
d74918a
docs(coroutine-context-and-dispatchers): Fix the indentation
jamhour1g Feb 12, 2025
f10ba77
docs(coroutine-context-and-dispatchers): Use present tense for ongoin…
jamhour1g Feb 12, 2025
865654b
docs(coroutine-context-and-dispatchers): Emphasize that all coroutine…
jamhour1g Feb 12, 2025
72690d4
docs(coroutine-context-and-dispatchers): Fix grammatical error
jamhour1g Feb 12, 2025
3378775
docs(CoroutineScope): Clarify coroutine lifetime emphasis
jamhour1g Feb 13, 2025
fbc4e66
docs(Job): Restore correct conjunction for coroutine completion condi…
jamhour1g Feb 13, 2025
c8ed37a
docs(ThreadContextElement): Remove redundant "on" for grammatical acc…
jamhour1g Feb 13, 2025
14e5526
docs(CoroutineScope): Add a missing article
jamhour1g Feb 14, 2025
7aeb177
docs: Revert linking to external references
jamhour1g Feb 14, 2025
69cec21
docs(coroutine-context-and-dispatchers): Remove vague phrasing and im…
jamhour1g Feb 14, 2025
c538102
docs(cancellation-and-timeouts): Update the Index using knit
jamhour1g Feb 14, 2025
35f545a
docs(coroutine-context-and-dispatchers): Remove the reference to use,…
jamhour1g Feb 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 17 additions & 10 deletions docs/topics/cancellation-and-timeouts.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ This section covers coroutine cancellation and timeouts.

## Cancelling coroutine execution

In a long-running application you might need fine-grained control on your background coroutines.
For example, a user might have closed the page that launched a coroutine and now its result
In a long-running application, you might need fine-grained control on your background coroutines.
For example, a user might have closed the page that launched a coroutine, and now its result
is no longer needed and its operation can be cancelled.
The [launch] function returns a [Job] that can be used to cancel the running coroutine:

Expand Down Expand Up @@ -142,9 +142,12 @@ which does not rethrow [CancellationException].

## Making computation code cancellable

There are two approaches to making computation code cancellable. The first one is to periodically
invoke a suspending function that checks for cancellation. There is a [yield] function that is a good choice for that purpose.
The other one is to explicitly check the cancellation status. Let us try the latter approach.
There are two approaches to making computation code cancellable.
The first one is periodically invoking a suspending function that checks for cancellation.
There are the [yield] and [ensureActive]
functions, which are great choices for that purpose.
The other one is explicitly checking the cancellation status using [isActive].
Let us try the latter approach.

Replace `while (i < 5)` in the previous example with `while (isActive)` and rerun it.

Expand All @@ -158,7 +161,7 @@ fun main() = runBlocking {
var nextPrintTime = startTime
var i = 0
while (isActive) { // cancellable computation loop
// print a message twice a second
// prints a message twice a second
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime += 500L
Expand Down Expand Up @@ -192,8 +195,10 @@ main: Now I can quit.
## Closing resources with finally

Cancellable suspending functions throw [CancellationException] on cancellation, which can be handled in
the usual way. For example, the `try {...} finally {...}` expression and Kotlin's `use` function execute their
finalization actions normally when a coroutine is cancelled:
the usual way.
For example,
the `try {...} finally {...}` expression and Kotlin's [use]
function execute their finalization actions normally when a coroutine is cancelled:

```kotlin
import kotlinx.coroutines.*
Expand Down Expand Up @@ -241,7 +246,7 @@ main: Now I can quit.

Any attempt to use a suspending function in the `finally` block of the previous example causes
[CancellationException], because the coroutine running this code is cancelled. Usually, this is not a
problem, since all well-behaving closing operations (closing a file, cancelling a job, or closing any kind of a
problem, since all well-behaved closing operations (closing a file, cancelling a job, or closing any kind of
communication channel) are usually non-blocking and do not involve any suspending functions. However, in the
rare case when you need to suspend in a cancelled coroutine you can wrap the corresponding code in
`withContext(NonCancellable) {...}` using [withContext] function and [NonCancellable] context as the following example shows:
Expand Down Expand Up @@ -327,7 +332,7 @@ Exception in thread "main" kotlinx.coroutines.TimeoutCancellationException: Time

<!--- TEST STARTS_WITH -->

The `TimeoutCancellationException` that is thrown by [withTimeout] is a subclass of [CancellationException].
The [TimeoutCancellationException] that is thrown by [withTimeout] is a subclass of [CancellationException].
We have not seen its stack trace printed on the console before. That is because
inside a cancelled coroutine `CancellationException` is considered to be a normal reason for coroutine completion.
However, in this example we have used `withTimeout` right inside the `main` function.
Expand Down Expand Up @@ -489,11 +494,13 @@ This example always prints zero. Resources do not leak.
[Job.join]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html
[CancellationException]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html
[yield]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html
[ensureActive]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/ensure-active.html
[isActive]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/is-active.html
[CoroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
[withContext]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
[NonCancellable]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-non-cancellable/index.html
[withTimeout]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout.html
[TimeoutCancellationException]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-timeout-cancellation-exception/index.html
[withTimeoutOrNull]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout-or-null.html

<!--- END -->
12 changes: 7 additions & 5 deletions docs/topics/composing-suspending-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,9 @@ Note that if we just call [await][Deferred.await] in `println` without first cal
coroutines, this will lead to sequential behavior, since [await][Deferred.await] starts the coroutine
execution and waits for its finish, which is not the intended use-case for laziness.
The use-case for `async(start = CoroutineStart.LAZY)` is a replacement for the
standard `lazy` function in cases when computation of the value involves suspending functions.
standard [lazy] function in cases
when computation of the value involves suspending functions.


## Async-style functions

Expand Down Expand Up @@ -291,10 +293,10 @@ concurrency, as shown in the section below.

## Structured concurrency with async

Let us take the [Concurrent using async](#concurrent-using-async) example and extract a function that
concurrently performs `doSomethingUsefulOne` and `doSomethingUsefulTwo` and returns the sum of their results.
Because the [async] coroutine builder is defined as an extension on [CoroutineScope], we need to have it in the
scope and that is what the [coroutineScope][_coroutineScope] function provides:
Let's refactor the [Concurrent using async](#concurrent-using-async) example into a function that runs
`doSomethingUsefulOne` and `doSomethingUsefulTwo` concurrently and returns their combined results.
Since [async] is a [CoroutineScope] extension,
we'll use the [coroutineScope][_coroutineScope] function to provide the necessary scope:

```kotlin
suspend fun concurrentSum(): Int = coroutineScope {
Expand Down
68 changes: 41 additions & 27 deletions docs/topics/coroutine-context-and-dispatchers.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,9 +246,15 @@ fun main() {
>
{style="note"}

It demonstrates several new techniques. One is using [runBlocking] with an explicitly specified context, and
the other one is using the [withContext] function to change the context of a coroutine while still staying in the
same coroutine, as you can see in the output below:
The example above demonstrates new techniques in coroutine usage.

The first technique shows how to use [runBlocking] with a specified context.
The second technique involves calling [withContext],
which may suspend the current coroutine and switch to a new context—provided the new context differs from the existing one.
Specifically, if you specify a different [CoroutineDispatcher], extra dispatches are required:
the block is scheduled on the new dispatcher, and once it finishes, execution returns to the original dispatcher.

As a result, the output of the above code is:

```text
[Ctx1 @coroutine#1] Started in ctx1
Expand All @@ -258,8 +264,8 @@ same coroutine, as you can see in the output below:

<!--- TEST -->

Note that this example also uses the `use` function from the Kotlin standard library to release threads
created with [newSingleThreadContext] when they are no longer needed.
The example above uses the `use` function from the Kotlin standard library
to properly release thread resources created by [newSingleThreadContext] when they're no longer needed.

## Job in the context

Expand All @@ -281,7 +287,7 @@ fun main() = runBlocking<Unit> {
>
{style="note"}

In the [debug mode](#debugging-coroutines-and-threads), it outputs something like this:
In [debug mode](#debugging-coroutines-and-threads), it outputs something like this:

```
My job is "coroutine#1":BlockingCoroutine{Active}@6d311334
Expand All @@ -300,12 +306,12 @@ 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, this parent-child relation can be explicitly overriden in one of two ways:
However, this parent-child relation can be explicitly overridden 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 shown in the example below),
then it overrides the `Job` of the parent scope.
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 shown in the example below),
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.

Expand Down Expand Up @@ -356,7 +362,8 @@ job1: I am not affected by cancellation of the request

## Parental responsibilities

A parent coroutine always waits for completion of all its children. A parent does not have to explicitly track
A parent coroutine always waits for the completion of all its children.
A parent does not have to explicitly track
all the children it launches, and it does not have to use [Job.join] to wait for them at the end:

```kotlin
Expand Down Expand Up @@ -480,11 +487,15 @@ I'm working in thread DefaultDispatcher-worker-1 @test#2

## Coroutine scope

Let us put our knowledge about contexts, children and jobs together. Assume that our application has
an object with a lifecycle, but that object is not a coroutine. For example, we are writing an Android application
and launch various coroutines in the context of an Android activity to perform asynchronous operations to fetch
and update data, do animations, etc. All of these coroutines must be cancelled when the activity is destroyed
to avoid memory leaks. We, of course, can manipulate contexts and jobs manually to tie the lifecycles of the activity
Let us put our knowledge about contexts, children, and jobs together.
Assume that our application has an object with a lifecycle, but that object is not a coroutine.
For example,
we are writing an Android application,
and launching various coroutines in the context of an Android activity
to perform asynchronous operations to fetch and update data,
do animations, etc. These coroutines must be cancelled when the activity is destroyed
to avoid memory leaks.
We, of course, can manipulate contexts and jobs manually to tie the lifecycles of the activity
and its coroutines, but `kotlinx.coroutines` provides an abstraction encapsulating that: [CoroutineScope].
You should be already familiar with the coroutine scope as all coroutine builders are declared as extensions on it.

Expand Down Expand Up @@ -521,8 +532,9 @@ For the demo, we launch ten coroutines that delay for a different time:
```

In our main function we create the activity, call our test `doSomething` function, and destroy the activity after 500ms.
This cancels all the coroutines that were launched from `doSomething`. We can see that because after the destruction
of the activity no more messages are printed, even if we wait a little longer.
This cancels all the coroutines that were launched from `doSomething`.
We can see that because after the destruction
of the activity, no more messages are printed, even if we wait a little longer.

<!--- CLEAR -->

Expand Down Expand Up @@ -577,16 +589,16 @@ Destroying activity!
<!--- TEST -->

As you can see, only the first two coroutines print a message and the others are cancelled
by a single invocation of `job.cancel()` in `Activity.destroy()`.
by a single invocation of [`mainScope.cancel()`][CoroutineScope.cancel] in `Activity.destroy()`.

> Note, that Android has first-party support for coroutine scope in all entities with the lifecycle.
> Note that Android has first-party support for coroutine scope in all entities with the lifecycle.
> See [the corresponding documentation](https://developer.android.com/topic/libraries/architecture/coroutines#lifecyclescope).
>
{style="note"}

### Thread-local data

Sometimes it is convenient to have an ability to pass some thread-local data to or between coroutines.
Sometimes it is convenient to be able to pass some thread-local data to or between coroutines.
However, since they are not bound to any particular thread, this will likely lead to boilerplate if done manually.

For [`ThreadLocal`](https://docs.oracle.com/javase/8/docs/api/java/lang/ThreadLocal.html),
Expand Down Expand Up @@ -620,8 +632,8 @@ fun main() = runBlocking<Unit> {
>
{style="note"}

In this example we launch a new coroutine in a background thread pool using [Dispatchers.Default], so
it works on a different thread from the thread pool, but it still has the value of the thread local variable
In this example, we launch a new coroutine in a background thread pool using [Dispatchers.Default], so
it works on different threads from the thread pool, but it still has the value of the thread local variable
that we specified using `threadLocal.asContextElement(value = "launch")`,
no matter which thread the coroutine is executed on.
Thus, the output (with [debug](#debugging-coroutines-and-threads)) is:
Expand All @@ -636,7 +648,7 @@ Post-main, current thread: Thread[main @coroutine#1,5,main], thread local value:
<!--- TEST FLEXIBLE_THREAD -->

It's easy to forget to set the corresponding context element. The thread-local variable accessed from the coroutine may
then have an unexpected value, if the thread running the coroutine is different.
then have an unexpected value if the thread running the coroutine is different.
To avoid such situations, it is recommended to use the [ensurePresent] method
and fail-fast on improper usages.

Expand All @@ -646,11 +658,12 @@ It has one key limitation, though: when a thread-local is mutated, a new value i
Use [withContext] to update the value of the thread-local in a coroutine, see [asContextElement] for more details.

Alternatively, a value can be stored in a mutable box like `class Counter(var i: Int)`, which is, in turn,
stored in a thread-local variable. However, in this case you are fully responsible to synchronize
stored in a thread-local variable.
However, in this case, you are fully responsible to synchronize
potentially concurrent modifications to the variable in this mutable box.

For advanced usage, for example for integration with logging MDC, transactional contexts or any other libraries
which internally use thread-locals for passing data, see the documentation of the [ThreadContextElement] interface
For advanced usage, for example, for integration with logging MDC, transactional contexts or any other libraries
that internally use thread-locals for passing data, see the documentation of the [ThreadContextElement] interface
that should be implemented.

<!--- MODULE kotlinx-coroutines-core -->
Expand All @@ -676,6 +689,7 @@ that should be implemented.
[CoroutineScope()]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope.html
[MainScope()]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-main-scope.html
[Dispatchers.Main]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-main.html
[CoroutineScope.cancel]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel.html
[asContextElement]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/as-context-element.html
[ensurePresent]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/ensure-present.html
[ThreadContextElement]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-thread-context-element/index.html
Expand Down
4 changes: 2 additions & 2 deletions docs/topics/coroutines-basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,8 @@ Done
## An explicit job

A [launch] coroutine builder returns a [Job] object that is a handle to the launched coroutine and can be
used to explicitly wait for its completion. For example, you can wait for completion of the child coroutine
and then print "Done" string:
used to wait for its completion explicitly.
For example, you can wait for the completion of the child coroutine and then print the "Done" string:

```kotlin
import kotlinx.coroutines.*
Expand Down
9 changes: 5 additions & 4 deletions kotlinx-coroutines-core/common/src/CompletableJob.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,13 @@ public interface CompletableJob : Job {
*
* Subsequent invocations of this function have no effect and always produce `false`.
*
* This function transitions this job into _cancelled_ state if it was not completed or cancelled yet.
* However, that if this job has children, then it transitions into _cancelling_ state and becomes _cancelled_
* This function transitions this job into the _cancelled_ state if it has not been _completed_ or _cancelled_ yet.
* However, if this job has children, then it transitions into the _cancelling_ state and becomes _cancelled_
* once all its children are [complete][isCompleted]. See [Job] for details.
*
* Its responsibility of the caller to properly handle and report the given [exception], all job's children will receive
* a [CancellationException] with the [exception] as a cause for the sake of diagnostic.
* It is the responsibility of the caller to properly handle and report the given [exception].
* All the job’s children will receive a [CancellationException] with
* the [exception] as a cause for the sake of diagnosis.
*/
public fun completeExceptionally(exception: Throwable): Boolean
}
Loading