Skip to content

Commit 32d9532

Browse files
committed
defer coroutine builder is renamed to async.
`lazyDefer` is deprecated, `async` has an optional `start` parameter instead. `LazyDeferred` interface is deprecated, lazy start functionality is integrated into `Job` interface. `launch` has an optional `start` parameter for lazily started coroutines. `Job.start` and `Job.isCompleted` are introduced. `Job.join` is now a member function. Internal `JobSupport` state machine is enhanced to support _new_ (not-started-yet) state. So, lazy coroutines do not need a separate state variable to track their started/not-started (new/active) status. Example on async-style functions is added to coroutines guide.
1 parent b26d731 commit 32d9532

File tree

27 files changed

+634
-367
lines changed

27 files changed

+634
-367
lines changed

coroutines-guide.md

Lines changed: 69 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,9 @@ This is a short guide on core features of `kotlinx.coroutines` with a series of
4040
* [Timeout](#timeout)
4141
* [Composing suspending functions](#composing-suspending-functions)
4242
* [Sequential by default](#sequential-by-default)
43-
* [Concurrent using deferred value](#concurrent-using-deferred-value)
44-
* [Lazily deferred value](#lazily-deferred-value)
43+
* [Concurrent using async](#concurrent-using-async)
44+
* [Lazily started async](#lazily-started-async)
45+
* [Async-style functions](#async-style-functions)
4546
* [Coroutine context and dispatchers](#coroutine-context-and-dispatchers)
4647
* [Dispatchers and threads](#dispatchers-and-threads)
4748
* [Unconfined vs confined dispatcher](#unconfined-vs-confined-dispatcher)
@@ -511,7 +512,7 @@ In practise we do this if we use the results of the first function to make a dec
511512
to invoke the second one or to decide on how to invoke it.
512513

513514
We just use a normal sequential invocation, because the code in the coroutine, just like in the regular
514-
code, is _sequential_ by default. The following example demonstrates that by measuring the total
515+
code, is _sequential_ by default. The following example demonstrates it by measuring the total
515516
time it takes to execute both suspending functions:
516517

517518
```kotlin
@@ -534,22 +535,22 @@ The answer is 42
534535
Completed in 2017 ms
535536
```
536537

537-
### Concurrent using deferred value
538+
### Concurrent using async
538539

539540
What if there are no dependencies between invocation of `doSomethingUsefulOne` and `doSomethingUsefulTwo` and
540-
we want to get the answer faster, by doing both _concurrently_? This is where `defer` comes to helps.
541+
we want to get the answer faster, by doing both _concurrently_? This is where `async` comes to help.
541542

542-
Conceptually, `defer` is just like `launch`. It starts a separate coroutine which is a light-weight thread
543+
Conceptually, `async` is just like `launch`. It starts a separate coroutine which is a light-weight thread
543544
that works concurrently with all the other coroutines. The difference is that `launch` returns a `Job` and
544-
does not carry any resulting value, while `defer` returns a `Deferred` -- a kind of light-weight non-blocking future
545-
that represent a promise to provide result later. You can use `.await()` on a deferred value to get its eventual result,
545+
does not carry any resulting value, while `async` returns a `Deferred` -- a light-weight non-blocking future
546+
that represents a promise to provide a result later. You can use `.await()` on a deferred value to get its eventual result,
546547
but `Deferred` is also a `Job`, so you can cancel it if needed.
547548

548549
```kotlin
549550
fun main(args: Array<String>) = runBlocking<Unit> {
550551
val time = measureTimeMillis {
551-
val one = defer(CommonPool) { doSomethingUsefulOne() }
552-
val two = defer(CommonPool) { doSomethingUsefulTwo() }
552+
val one = async(CommonPool) { doSomethingUsefulOne() }
553+
val two = async(CommonPool) { doSomethingUsefulTwo() }
553554
println("The answer is ${one.await() + two.await()}")
554555
}
555556
println("Completed in $time ms")
@@ -568,17 +569,17 @@ Completed in 1017 ms
568569
This is twice as fast, because we have concurrent execution of two coroutines.
569570
Note, that concurrency with coroutines is always explicit.
570571

571-
### Lazily deferred value
572+
### Lazily started async
572573

573-
There is a lazy alternative to `defer` that is called `lazyDefer`. It is just like `defer`, but it
574-
starts coroutine only when its result is needed by some `await` or if a special `start` function
575-
is invoked. Run the following example:
574+
There is a laziness option to `async` with `start = false` parameter.
575+
It starts coroutine only when its result is needed by some `await` or if a `start` function
576+
is invoked. Run the following example that differs from the previous one only by this option:
576577

577578
```kotlin
578579
fun main(args: Array<String>) = runBlocking<Unit> {
579580
val time = measureTimeMillis {
580-
val one = lazyDefer(CommonPool) { doSomethingUsefulOne() }
581-
val two = lazyDefer(CommonPool) { doSomethingUsefulTwo() }
581+
val one = async(CommonPool, start = false) { doSomethingUsefulOne() }
582+
val two = async(CommonPool, start = false) { doSomethingUsefulTwo() }
582583
println("The answer is ${one.await() + two.await()}")
583584
}
584585
println("Completed in $time ms")
@@ -594,13 +595,57 @@ The answer is 42
594595
Completed in 2017 ms
595596
```
596597

597-
So, we are back to two sequential execution, because we _first_ await for the `one` deferred, _and then_ await
598-
for the second one. It is not the intended use-case for `lazyDefer`. It is designed as a replacement for
599-
the standard `lazy` function in cases when computation of the value involve suspending functions.
598+
So, we are back to sequential execution, because we _first_ start and await for `one`, _and then_ start and await
599+
for `two`. It is not the intended use-case for laziness. It is designed as a replacement for
600+
the standard `lazy` function in cases when computation of the value involves suspending functions.
601+
602+
### Async-style functions
603+
604+
We can define async-style functions that invoke `doSomethingUsefulOne` and `doSomethingUsefulTwo`
605+
_asynchronously_ using `async` coroutine builder. It is a good style to name such functions with
606+
either "async" prefix of "Async" suffix to highlight the fact that they only start asynchronous
607+
computation and one needs to use the resulting deferred value to get the result.
608+
609+
```kotlin
610+
// The result type of asyncSomethingUsefulOne is Deferred<Int>
611+
fun asyncSomethingUsefulOne() = async(CommonPool) {
612+
doSomethingUsefulOne()
613+
}
614+
615+
// The result type of asyncSomethingUsefulTwo is Deferred<Int>
616+
fun asyncSomethingUsefulTwo() = async(CommonPool) {
617+
doSomethingUsefulTwo()
618+
}
619+
```
620+
621+
Note, that these `asyncXXX` function are **not** _suspending_ functions. They can be used from anywhere.
622+
However, their use always implies asynchronous (here meaning _concurrent_) execution of their action
623+
with the invoking code.
624+
625+
The following example shows their use outside of coroutine:
626+
627+
```kotlin
628+
// note, that we don't have `runBlocking` to the right of `main` in this example
629+
fun main(args: Array<String>) {
630+
val time = measureTimeMillis {
631+
// we can initiate async actions outside of a coroutine
632+
val one = asyncSomethingUsefulOne()
633+
val two = asyncSomethingUsefulTwo()
634+
// but waiting for a result must involve either suspending or blocking.
635+
// here we use `runBlocking { ... }` to block the main thread while waiting for the result
636+
runBlocking {
637+
println("The answer is ${one.await() + two.await()}")
638+
}
639+
}
640+
println("Completed in $time ms")
641+
}
642+
```
643+
644+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-compose-04.kt)
600645
601646
## Coroutine context and dispatchers
602647

603-
We've already seen `launch(CommonPool) {...}`, `defer(CommonPool) {...}`, `run(NonCancellable) {...}`, etc.
648+
We've already seen `launch(CommonPool) {...}`, `async(CommonPool) {...}`, `run(NonCancellable) {...}`, etc.
604649
In these code snippets `CommonPool` and `NonCancellable` are _coroutine contexts_.
605650
This section covers other available choices.
606651

@@ -701,11 +746,11 @@ Run the following code with `-Dkotlinx.coroutines.debug` JVM option:
701746
fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
702747

703748
fun main(args: Array<String>) = runBlocking<Unit> {
704-
val a = defer(context) {
749+
val a = async(context) {
705750
log("I'm computing a piece of the answer")
706751
6
707752
}
708-
val b = defer(context) {
753+
val b = async(context) {
709754
log("I'm computing another piece of the answer")
710755
7
711756
}
@@ -879,12 +924,12 @@ fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
879924
fun main(args: Array<String>) = runBlocking(CoroutineName("main")) {
880925
log("Started main coroutine")
881926
// run two background value computations
882-
val v1 = defer(CommonPool + CoroutineName("v1coroutine")) {
927+
val v1 = async(CommonPool + CoroutineName("v1coroutine")) {
883928
log("Computing v1")
884929
delay(500)
885930
252
886931
}
887-
val v2 = defer(CommonPool + CoroutineName("v2coroutine")) {
932+
val v2 = async(CommonPool + CoroutineName("v2coroutine")) {
888933
log("Computing v2")
889934
delay(1000)
890935
6

kotlinx-coroutines-core/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ General-purpose coroutine builders and contexts.
99
* `launch(context) {...}` to start a coroutine in the given context and get reference to its `Job`.
1010
* `run(context) {...}` to switch to a different context inside a coroutine.
1111
* `runBlocking {...}` to use asynchronous Kotlin APIs from a thread-blocking code.
12-
* `defer(context) {...}` and `lazyDefer(context) {...}` to get a deferred result of coroutine execution in a
12+
* `async(context) {...}` to get a deferred result of coroutine execution in a
1313
non-blocking way via a light-weight future interface called `Deferred`.
1414
* `delay(...)` for a non-blocking sleep in coroutines and
1515
`yield()` to release a thread in single-threaded dispatchers.

kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,32 @@ import kotlin.coroutines.experimental.*
2323

2424
/**
2525
* Launches new coroutine without blocking current thread and returns a reference to the coroutine as a [Job].
26-
* The running coroutine is cancelled when the resulting job is [cancelled][Job.cancel].
26+
* The coroutine is cancelled when the resulting job is [cancelled][Job.cancel].
2727
*
2828
* The [context] for the new coroutine must be explicitly specified.
2929
* See [CoroutineDispatcher] for the standard [context] implementations that are provided by `kotlinx.coroutines`.
3030
* The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
3131
* in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
3232
*
33+
* An optional [start] parameter can be set to `false` to start coroutine _lazily_. When `start = false`,
34+
* the coroutine [Job] is created in _new_ state. It can be explicitly started with [start][Job.start] function
35+
* and will be started implicitly on the first invocation of [join][Job.join].
36+
*
3337
* Uncaught exceptions in this coroutine cancel parent job in the context by default
3438
* (unless [CoroutineExceptionHandler] is explicitly specified), which means that when `launch` is used with
3539
* the context of another coroutine, then any uncaught exception leads to the cancellation of parent coroutine.
3640
*
3741
* See [newCoroutineContext] for a description of debugging facilities that are available for newly created coroutine.
3842
*/
39-
fun launch(context: CoroutineContext, block: suspend CoroutineScope.() -> Unit): Job =
40-
StandaloneCoroutine(newCoroutineContext(context)).apply {
41-
initParentJob(context[Job])
42-
block.startCoroutine(this, this)
43-
}
43+
fun launch(context: CoroutineContext, start: Boolean = true, block: suspend CoroutineScope.() -> Unit): Job {
44+
val newContext = newCoroutineContext(context)
45+
val coroutine = if (start)
46+
StandaloneCoroutine(newContext, active = true) else
47+
LazyStandaloneCoroutine(newContext, block)
48+
coroutine.initParentJob(context[Job])
49+
if (start) block.startCoroutine(coroutine, coroutine)
50+
return coroutine
51+
}
4452

4553
/**
4654
* Calls the specified suspending block with a given coroutine context, suspends until it completes, and returns
@@ -57,7 +65,7 @@ public suspend fun <T> run(context: CoroutineContext, block: suspend CoroutineSc
5765
}
5866

5967
/**
60-
* Runs new coroutine and *blocks* current thread *interruptibly* until its completion.
68+
* Runs new coroutine and **blocks** current thread _interruptibly_ until its completion.
6169
* This function should not be used from coroutine. It is designed to bridge regular blocking code
6270
* to libraries that are written in suspending style, to be used in `main` functions and in tests.
6371
*
@@ -84,15 +92,25 @@ public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, bl
8492

8593
// --------------- implementation ---------------
8694

87-
private class StandaloneCoroutine(
88-
val parentContext: CoroutineContext
89-
) : AbstractCoroutine<Unit>(parentContext) {
95+
private open class StandaloneCoroutine(
96+
val parentContext: CoroutineContext,
97+
active: Boolean
98+
) : AbstractCoroutine<Unit>(parentContext, active) {
9099
override fun afterCompletion(state: Any?) {
91100
// note the use of the parent's job context below!
92101
if (state is CompletedExceptionally) handleCoroutineException(parentContext, state.exception)
93102
}
94103
}
95104

105+
private class LazyStandaloneCoroutine(
106+
parentContext: CoroutineContext,
107+
val block: suspend CoroutineScope.() -> Unit
108+
) : StandaloneCoroutine(parentContext, active = false) {
109+
override fun onStart() {
110+
block.startCoroutine(this, this)
111+
}
112+
}
113+
96114
private class InnerCoroutine<in T>(
97115
override val context: CoroutineContext,
98116
continuation: Continuation<T>
@@ -104,7 +122,7 @@ private class BlockingCoroutine<T>(
104122
context: CoroutineContext,
105123
val blockedThread: Thread,
106124
val hasPrivateEventLoop: Boolean
107-
) : AbstractCoroutine<T>(context) {
125+
) : AbstractCoroutine<T>(context, active = true) {
108126
val eventLoop: EventLoop? = context[ContinuationInterceptor] as? EventLoop
109127

110128
override fun afterCompletion(state: Any?) {

kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ import kotlin.coroutines.experimental.suspendCoroutine
3131
* with the specified cancel cause.
3232
*
3333
* Cancellable continuation has three states:
34-
* * _Active_ (initial state) -- [isActive] `true`, [isCancelled] `false`.
35-
* * _Resumed_ (final _completed_ state) -- [isActive] `false`, [isCancelled] `false`.
36-
* * _Canceled_ (final _completed_ state) -- [isActive] `false`, [isCancelled] `true`.
34+
*
35+
* | **State** | [isActive] | [isCompleted] | [isCancelled] |
36+
* | _Active_ (initial state) | `true` | `false` | `false` |
37+
* | _Resumed_ (final _completed_ state) | `false` | `true` | `false` |
38+
* | _Canceled_ (final _completed_ state)| `false` | `true` | `true` |
3739
*
3840
* Invocation of [cancel] transitions this continuation from _active_ to _cancelled_ state, while
3941
* invocation of [resume] or [resumeWithException] transitions it from _active_ to _resumed_ state.
@@ -43,7 +45,9 @@ import kotlin.coroutines.experimental.suspendCoroutine
4345
*/
4446
public interface CancellableContinuation<in T> : Continuation<T>, Job {
4547
/**
46-
* Returns `true` if this continuation was [cancelled][cancel]. It implies that [isActive] is `false`.
48+
* Returns `true` if this continuation was [cancelled][cancel].
49+
*
50+
* It implies that [isActive] is `false` and [isCompleted] is `true`.
4751
*/
4852
val isCancelled: Boolean
4953

@@ -105,7 +109,7 @@ internal fun getParentJobOrAbort(cont: Continuation<*>): Job? {
105109
internal class SafeCancellableContinuation<in T>(
106110
private val delegate: Continuation<T>,
107111
private val parentJob: Job?
108-
) : AbstractCoroutine<T>(delegate.context), CancellableContinuation<T> {
112+
) : AbstractCoroutine<T>(delegate.context, active = true), CancellableContinuation<T> {
109113
// only updated from the thread that invoked suspendCancellableCoroutine
110114

111115
@Volatile
@@ -144,7 +148,7 @@ internal class SafeCancellableContinuation<in T>(
144148
while (true) { // lock-free loop on state
145149
val state = getState() // atomic read
146150
when (state) {
147-
is Active -> if (tryUpdateState(state, value)) return state
151+
is Incomplete -> if (tryUpdateState(state, value)) return state
148152
else -> return null // cannot resume -- not active anymore
149153
}
150154
}
@@ -154,7 +158,7 @@ internal class SafeCancellableContinuation<in T>(
154158
while (true) { // lock-free loop on state
155159
val state = getState() // atomic read
156160
when (state) {
157-
is Active -> if (tryUpdateState(state, CompletedExceptionally(exception))) return state
161+
is Incomplete -> if (tryUpdateState(state, CompletedExceptionally(exception))) return state
158162
else -> return null // cannot resume -- not active anymore
159163
}
160164
}

kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ public object Unconfined : CoroutineDispatcher() {
4545
override fun dispatch(context: CoroutineContext, block: Runnable) { throw UnsupportedOperationException() }
4646
}
4747

48+
/**
49+
* **Deprecated**: `Here` was renamed to `Unconfined`.
50+
*/
4851
@Deprecated(message = "`Here` was renamed to `Unconfined`",
4952
replaceWith = ReplaceWith(expression = "Unconfined"))
5053
public typealias Here = Unconfined

kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineDispatcher.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,22 @@ public abstract class CoroutineDispatcher :
5252
public abstract fun dispatch(context: CoroutineContext, block: Runnable)
5353

5454
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
55-
DispatchedContinuation<T>(this, continuation)
55+
DispatchedContinuation(this, continuation)
5656

57+
/**
58+
* **Error**: Operator '+' on two CoroutineDispatcher objects is meaningless.
59+
* CoroutineDispatcher is a coroutine context element and `+` is a set-sum operator for coroutine contexts.
60+
* The dispatcher to the right of `+` just replaces the dispatcher the left of `+`.
61+
*/
5762
@Suppress("DeprecatedCallableAddReplaceWith")
5863
@Deprecated(message = "Operator '+' on two CoroutineDispatcher objects is meaningless. " +
5964
"CoroutineDispatcher is a coroutine context element and `+` is a set-sum operator for coroutine contexts. " +
60-
"The dispatcher to the right of `+` just replaces the dispacher the left of `+`.",
65+
"The dispatcher to the right of `+` just replaces the dispatcher the left of `+`.",
6166
level = DeprecationLevel.ERROR)
6267
public operator fun plus(other: CoroutineDispatcher) = other
6368
}
6469

65-
internal class DispatchedContinuation<T>(
70+
internal class DispatchedContinuation<in T>(
6671
val dispatcher: CoroutineDispatcher,
6772
val continuation: Continuation<T>
6873
): Continuation<T> by continuation {
@@ -100,7 +105,7 @@ internal class DispatchedContinuation<T>(
100105
if (dispatcher.isDispatchNeeded(context))
101106
dispatcher.dispatch(context, Runnable {
102107
withCoroutineContext(context) {
103-
if (job?.isActive == false)
108+
if (job?.isCompleted == true)
104109
continuation.resumeWithException(job.getCompletionException())
105110
else
106111
continuation.resume(value)

0 commit comments

Comments
 (0)