Skip to content

Commit 87261a3

Browse files
elizarovrecheej
authored andcommitted
Update CoroutineScope docs (Kotlin#1882)
* Update CoroutineScope docs * Fixed scope examples in guides, added notes on first-party support in Android. * Simplified scopes section in UI guide since it is mostly irrelevant. Fixes Kotlin#1581
1 parent 12806a5 commit 87261a3

File tree

4 files changed

+63
-102
lines changed

4 files changed

+63
-102
lines changed

docs/coroutine-context-and-dispatchers.md

+11-23
Original file line numberDiff line numberDiff line change
@@ -501,21 +501,8 @@ class Activity {
501501

502502
</div>
503503

504-
Alternatively, we can implement the [CoroutineScope] interface in this `Activity` class. The best way to do it is
505-
to use delegation with default factory functions.
506-
We also can combine the desired dispatcher (we used [Dispatchers.Default] in this example) with the scope:
507-
508-
<div class="sample" markdown="1" theme="idea" data-highlight-only>
509-
510-
```kotlin
511-
class Activity : CoroutineScope by CoroutineScope(Dispatchers.Default) {
512-
// to be continued ...
513-
```
514-
515-
</div>
516-
517-
Now, we can launch coroutines in the scope of this `Activity` without having to explicitly
518-
specify their context. For the demo, we launch ten coroutines that delay for a different time:
504+
Now, we can launch coroutines in the scope of this `Activity` using the defined `scope`.
505+
For the demo, we launch ten coroutines that delay for a different time:
519506

520507
<div class="sample" markdown="1" theme="idea" data-highlight-only>
521508

@@ -524,7 +511,7 @@ specify their context. For the demo, we launch ten coroutines that delay for a d
524511
fun doSomething() {
525512
// launch ten coroutines for a demo, each working for a different time
526513
repeat(10) { i ->
527-
launch {
514+
mainScope.launch {
528515
delay((i + 1) * 200L) // variable delay 200ms, 400ms, ... etc
529516
println("Coroutine $i is done")
530517
}
@@ -544,21 +531,19 @@ of the activity no more messages are printed, even if we wait a little longer.
544531
<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
545532

546533
```kotlin
547-
import kotlin.coroutines.*
548534
import kotlinx.coroutines.*
549535

550-
class Activity : CoroutineScope by CoroutineScope(Dispatchers.Default) {
551-
536+
class Activity {
537+
private val mainScope = CoroutineScope(Dispatchers.Default) // use Default for test purposes
538+
552539
fun destroy() {
553-
cancel() // Extension on CoroutineScope
540+
mainScope.cancel()
554541
}
555-
// to be continued ...
556542

557-
// class Activity continues
558543
fun doSomething() {
559544
// launch ten coroutines for a demo, each working for a different time
560545
repeat(10) { i ->
561-
launch {
546+
mainScope.launch {
562547
delay((i + 1) * 200L) // variable delay 200ms, 400ms, ... etc
563548
println("Coroutine $i is done")
564549
}
@@ -597,6 +582,9 @@ Destroying activity!
597582
As you can see, only the first two coroutines print a message and the others are cancelled
598583
by a single invocation of `job.cancel()` in `Activity.destroy()`.
599584

585+
> Note, that Android has first-party support for coroutine scope in all entities with the lifecycle.
586+
See [the corresponding documentation](https://developer.android.com/topic/libraries/architecture/coroutines#lifecyclescope).
587+
600588
### Thread-local data
601589

602590
Sometimes it is convenient to have an ability to pass some thread-local data to or between coroutines.

kotlinx-coroutines-core/common/src/CoroutineScope.kt

+25-13
Original file line numberDiff line numberDiff line change
@@ -10,37 +10,49 @@ import kotlin.coroutines.*
1010
import kotlin.coroutines.intrinsics.*
1111

1212
/**
13-
* Defines a scope for new coroutines. Every coroutine builder
13+
* Defines a scope for new coroutines. Every **coroutine builder** (like [launch], [async], etc)
1414
* is an extension on [CoroutineScope] and inherits its [coroutineContext][CoroutineScope.coroutineContext]
15-
* to automatically propagate both context elements and cancellation.
15+
* to automatically propagate all its elements and cancellation.
1616
*
1717
* The best ways to obtain a standalone instance of the scope are [CoroutineScope()] and [MainScope()] factory functions.
1818
* Additional context elements can be appended to the scope using the [plus][CoroutineScope.plus] operator.
1919
*
20+
* ### Convention for structured concurrency
21+
*
2022
* Manual implementation of this interface is not recommended, implementation by delegation should be preferred instead.
21-
* By convention, the [context of a scope][CoroutineScope.coroutineContext] should contain an instance of a [job][Job] to enforce structured concurrency.
23+
* By convention, the [context of a scope][CoroutineScope.coroutineContext] should contain an instance of a
24+
* [job][Job] to enforce the discipline of **structured concurrency** with propagation of cancellation.
2225
*
23-
* Every coroutine builder (like [launch][CoroutineScope.launch], [async][CoroutineScope.async], etc)
26+
* Every coroutine builder (like [launch], [async], etc)
2427
* and every scoping function (like [coroutineScope], [withContext], etc) provides _its own_ scope
2528
* with its own [Job] instance into the inner block of code it runs.
2629
* By convention, they all wait for all the coroutines inside their block to complete before completing themselves,
27-
* thus enforcing the discipline of **structured concurrency**.
30+
* thus enforcing the structured concurrency. See [Job] documentation for more details.
2831
*
29-
* [CoroutineScope] should be implemented (or used as a field) on entities with a well-defined lifecycle that are responsible
30-
* for launching children coroutines. Example of such entity on Android is Activity.
31-
* Usage of this interface may look like this:
32+
* ### Android usage
33+
*
34+
* Android has first-party support for coroutine scope in all entities with the lifecycle.
35+
* See [the corresponding documentation](https://developer.android.com/topic/libraries/architecture/coroutines#lifecyclescope).
36+
*
37+
* ### Custom usage
38+
*
39+
* [CoroutineScope] should be implemented or declared as a property on entities with a well-defined lifecycle that are
40+
* responsible for launching children coroutines, for example:
3241
*
3342
* ```
34-
* class MyActivity : AppCompatActivity(), CoroutineScope by MainScope() {
35-
* override fun onDestroy() {
36-
* cancel() // cancel is extension on CoroutineScope
43+
* class MyUIClass {
44+
* val scope = MainScope() // the scope of MyUIClass
45+
*
46+
* fun destroy() { // destroys an instance of MyUIClass
47+
* scope.cancel() // cancels all coroutines launched in this scope
48+
* // ... do the rest of cleanup here ...
3749
* }
3850
*
3951
* /*
40-
* * Note how coroutine builders are scoped: if activity is destroyed or any of the launched coroutines
52+
* * Note: if this instance is destroyed or any of the launched coroutines
4153
* * in this method throws an exception, then all nested coroutines are cancelled.
4254
* */
43-
* fun showSomeData() = launch { // <- extension on current activity, launched in the main thread
55+
* fun showSomeData() = scope.launch { // launched in the main thread
4456
* // ... here we can use suspending functions or coroutine builders with other dispatchers
4557
* draw(data) // draw in the main thread
4658
* }

kotlinx-coroutines-core/jvm/test/guide/example-context-10.kt

+5-7
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,19 @@
55
// This file was automatically generated from coroutine-context-and-dispatchers.md by Knit tool. Do not edit.
66
package kotlinx.coroutines.guide.exampleContext10
77

8-
import kotlin.coroutines.*
98
import kotlinx.coroutines.*
109

11-
class Activity : CoroutineScope by CoroutineScope(Dispatchers.Default) {
12-
10+
class Activity {
11+
private val mainScope = CoroutineScope(Dispatchers.Default) // use Default for test purposes
12+
1313
fun destroy() {
14-
cancel() // Extension on CoroutineScope
14+
mainScope.cancel()
1515
}
16-
// to be continued ...
1716

18-
// class Activity continues
1917
fun doSomething() {
2018
// launch ten coroutines for a demo, each working for a different time
2119
repeat(10) { i ->
22-
launch {
20+
mainScope.launch {
2321
delay((i + 1) * 200L) // variable delay 200ms, 400ms, ... etc
2422
println("Coroutine $i is done")
2523
}

ui/coroutines-guide-ui.md

+22-59
Original file line numberDiff line numberDiff line change
@@ -408,47 +408,40 @@ during UI freeze.
408408
### Structured concurrency, lifecycle and coroutine parent-child hierarchy
409409

410410
A typical UI application has a number of elements with a lifecycle. Windows, UI controls, activities, views, fragments
411-
and other visual elements are created and destroyed. A long-running coroutine, performing some IO or a background
412-
computation, can retain references to the corresponding UI elements for longer than it is needed, preventing garbage
411+
and other visual elements are created and destroyed. A long-running coroutine, performing some IO or a background
412+
computation, can retain references to the corresponding UI elements for longer than it is needed, preventing garbage
413413
collection of the whole trees of UI objects that were already destroyed and will not be displayed anymore.
414414

415-
The natural solution to this problem is to associate a [Job] object with each UI object that has a lifecycle and create
416-
all the coroutines in the context of this job. But passing associated job object to every coroutine builder is error-prone,
417-
it is easy to forget it. For this purpose, [CoroutineScope] interface could be implemented by UI owner, and then every
418-
coroutine builder defined as an extension on [CoroutineScope] inherits UI job without explicitly mentioning it.
419-
For the sake of simplicity, [MainScope()] factory can be used. It automatically provides `Dispatchers.Main` and parent
420-
job.
415+
The natural solution to this problem is to associate a [CoroutineScope] object with each UI object that has a
416+
lifecycle and create all the coroutines in the context of this scope.
417+
For the sake of simplicity, [MainScope()] factory can be used. It automatically provides `Dispatchers.Main` and
418+
a parent job for all the children coroutines.
419+
420+
For example, in Android application an `Activity` is initially _created_ and is _destroyed_ when it is no longer
421+
needed and when its memory must be released. A natural solution is to attach an
422+
instance of a `CoroutineScope` to an instance of an `Activity`:
421423

422-
For example, in Android application an `Activity` is initially _created_ and is _destroyed_ when it is no longer
423-
needed and when its memory must be released. A natural solution is to attach an
424-
instance of a `Job` to an instance of an `Activity`:
425424
<!--- CLEAR -->
426425

427426
```kotlin
428-
abstract class ScopedAppActivity: AppCompatActivity(), CoroutineScope by MainScope() {
427+
class MainActivity : AppCompatActivity() {
428+
private val scope = MainScope()
429+
429430
override fun onDestroy() {
430431
super.onDestroy()
431-
cancel() // CoroutineScope.cancel
432+
scope.cancel()
432433
}
433-
}
434-
```
435-
436-
Now, an activity that is associated with a job has to extend ScopedAppActivity
437-
438-
```kotlin
439-
class MainActivity : ScopedAppActivity() {
440434

441-
fun asyncShowData() = launch { // Is invoked in UI context with Activity's job as a parent
435+
fun asyncShowData() = scope.launch { // Is invoked in UI context with Activity's scope as a parent
442436
// actual implementation
443437
}
444438

445439
suspend fun showIOData() {
446-
val deferred = async(Dispatchers.IO) {
447-
// impl
440+
val data = withContext(Dispatchers.IO) {
441+
// compute data in background thread
448442
}
449443
withContext(Dispatchers.Main) {
450-
val data = deferred.await()
451-
// Show data in UI
444+
// Show data in UI
452445
}
453446
}
454447
}
@@ -457,43 +450,14 @@ class MainActivity : ScopedAppActivity() {
457450
Every coroutine launched from within a `MainActivity` has its job as a parent and is immediately cancelled when
458451
activity is destroyed.
459452

460-
To propagate activity scope to its views and presenters, multiple techniques can be used:
461-
- [coroutineScope] builder to provide a nested scope
462-
- Receive [CoroutineScope] in presenter method parameters
463-
- Make method extension on [CoroutineScope] (applicable only for top-level methods)
464-
465-
```kotlin
466-
class ActivityWithPresenters: ScopedAppActivity() {
467-
fun init() {
468-
val presenter = Presenter()
469-
val presenter2 = ScopedPresenter(this)
470-
}
471-
}
472-
473-
class Presenter {
474-
suspend fun loadData() = coroutineScope {
475-
// Nested scope of outer activity
476-
}
477-
478-
suspend fun loadData(uiScope: CoroutineScope) = uiScope.launch {
479-
// Invoked in the uiScope
480-
}
481-
}
482-
483-
class ScopedPresenter(scope: CoroutineScope): CoroutineScope by scope {
484-
fun loadData() = launch { // Extension on ActivityWithPresenters's scope
485-
}
486-
}
487-
488-
suspend fun CoroutineScope.launchInIO() = launch(Dispatchers.IO) {
489-
// Launched in the scope of the caller, but with IO dispatcher
490-
}
491-
```
453+
> Note, that Android has first-party support for coroutine scope in all entities with the lifecycle.
454+
See [the corresponding documentation](https://developer.android.com/topic/libraries/architecture/coroutines#lifecyclescope).
492455

493456
Parent-child relation between jobs forms a hierarchy. A coroutine that performs some background job on behalf of
494-
the view and in its context can create further children coroutines. The whole tree of coroutines gets cancelled
457+
the activity can create further children coroutines. The whole tree of coroutines gets cancelled
495458
when the parent job is cancelled. An example of that is shown in the
496459
["Children of a coroutine"](../docs/coroutine-context-and-dispatchers.md#children-of-a-coroutine) section of the guide to coroutines.
460+
497461
<!--- CLEAR -->
498462

499463
### Blocking operations
@@ -649,7 +613,6 @@ After delay
649613
[Job.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/cancel.html
650614
[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
651615
[MainScope()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-main-scope.html
652-
[coroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html
653616
[withContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
654617
[Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
655618
[CoroutineStart]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/index.html

0 commit comments

Comments
 (0)