Skip to content

Commit ecda27f

Browse files
committed
CoroutineStart enum is introduced in launch/async/actor
1 parent dc9fd1c commit ecda27f

File tree

17 files changed

+350
-46
lines changed

17 files changed

+350
-46
lines changed

coroutines-guide.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -652,16 +652,16 @@ Note, that concurrency with coroutines is always explicit.
652652

653653
### Lazily started async
654654

655-
There is a laziness option to [async] with `start = false` parameter.
655+
There is a laziness option to [async] with [CoroutineStart.LAZY] parameter.
656656
It starts coroutine only when its result is needed by some
657657
[await][Deferred.await] or if a [start][Job.start] function
658658
is invoked. Run the following example that differs from the previous one only by this option:
659659

660660
```kotlin
661661
fun main(args: Array<String>) = runBlocking<Unit> {
662662
val time = measureTimeMillis {
663-
val one = async(CommonPool, start = false) { doSomethingUsefulOne() }
664-
val two = async(CommonPool, start = false) { doSomethingUsefulTwo() }
663+
val one = async(CommonPool, CoroutineStart.LAZY) { doSomethingUsefulOne() }
664+
val two = async(CommonPool, CoroutineStart.LAZY) { doSomethingUsefulTwo() }
665665
println("The answer is ${one.await() + two.await()}")
666666
}
667667
println("Completed in $time ms")
@@ -2174,6 +2174,7 @@ Channel was closed
21742174
[withTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/with-timeout.html
21752175
[async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/async.html
21762176
[Deferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/index.html
2177+
[CoroutineStart.LAZY]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-coroutine-start/-l-a-z-y.html
21772178
[Deferred.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/await.html
21782179
[Job.start]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job/start.html
21792180
[CommonPool]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-common-pool/index.html

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

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package kotlinx.coroutines.experimental
1818

19+
import kotlinx.coroutines.experimental.intrinsics.startCoroutineUndispatched
1920
import java.util.concurrent.locks.LockSupport
2021
import kotlin.coroutines.experimental.*
2122
import kotlin.coroutines.experimental.intrinsics.startCoroutineUninterceptedOrReturn
@@ -32,7 +33,8 @@ import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn
3233
* The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
3334
* in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
3435
*
35-
* An optional [start] parameter can be set to `false` to start coroutine _lazily_. When `start = false`,
36+
* By default, the coroutine is immediately started.
37+
* An optional [start] parameter can be set to [CoroutineStart.LAZY] to start coroutine _lazily_. In this case,
3638
* the coroutine [Job] is created in _new_ state. It can be explicitly started with [start][Job.start] function
3739
* and will be started implicitly on the first invocation of [join][Job.join].
3840
*
@@ -41,17 +43,33 @@ import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn
4143
* the context of another coroutine, then any uncaught exception leads to the cancellation of parent coroutine.
4244
*
4345
* See [newCoroutineContext] for a description of debugging facilities that are available for newly created coroutine.
46+
47+
* @param context context of the coroutine
48+
* @param start coroutine start option
49+
* @param block the coroutine code
4450
*/
45-
fun launch(context: CoroutineContext, start: Boolean = true, block: suspend CoroutineScope.() -> Unit): Job {
51+
public fun launch(
52+
context: CoroutineContext,
53+
start: CoroutineStart = CoroutineStart.DEFAULT,
54+
block: suspend CoroutineScope.() -> Unit
55+
): Job {
4656
val newContext = newCoroutineContext(context)
47-
val coroutine = if (start)
48-
StandaloneCoroutine(newContext, active = true) else
49-
LazyStandaloneCoroutine(newContext, block)
57+
val coroutine = if (start.isLazy)
58+
LazyStandaloneCoroutine(newContext, block) else
59+
StandaloneCoroutine(newContext, active = true)
5060
coroutine.initParentJob(context[Job])
51-
if (start) block.startCoroutine(coroutine, coroutine)
61+
start(block, coroutine, coroutine)
5262
return coroutine
5363
}
5464

65+
/**
66+
* @suppress **Deprecated**: Use `start = CoroutineStart.XXX` parameter
67+
*/
68+
@Deprecated(message = "Use `start = CoroutineStart.XXX` parameter",
69+
replaceWith = ReplaceWith("launch(context, if (start) CoroutineStart.DEFAULT else CoroutineStart.LAZY, block)"))
70+
public fun launch(context: CoroutineContext, start: Boolean, block: suspend CoroutineScope.() -> Unit): Job =
71+
launch(context, if (start) CoroutineStart.DEFAULT else CoroutineStart.LAZY, block)
72+
5573
/**
5674
* Calls the specified suspending block with a given coroutine context, suspends until it completes, and returns
5775
* the result.

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ internal fun resetCoroutineId() {
4444
* It executes initial continuation of the coroutine _right here_ in the current call-frame
4545
* and let the coroutine resume in whatever thread that is used by the corresponding suspending function, without
4646
* mandating any specific threading policy.
47+
*
48+
* Note, that if you need your coroutine to be confined to a particular thread or a thread-pool after resumption,
49+
* but still want to execute it in the current call-frame until its first suspension, then you can use
50+
* an optional [CoroutineStart] parameter in coroutine builders like [launch] and [async] setting it to the
51+
* the value of [CoroutineStart.UNDISPATCHED].
4752
*/
4853
public object Unconfined : CoroutineDispatcher() {
4954
override fun isDispatchNeeded(context: CoroutineContext): Boolean = false

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ public abstract class CoroutineDispatcher :
6666
* While, C# approach seems to be more efficient, it ends up with recommendations like
6767
* "use `yield` if you need to ....". This is error-prone. JS-style approach is more consistent
6868
* and does not require programmers to think about whether they need to yield or not.
69+
*
70+
* However, coroutine builders like [launch] and [async] accept an optional [CoroutineStart]
71+
* parameter that allows one to optionally choose C#-style [CoroutineStart.UNDISPATCHED] behaviour
72+
* whenever it is needed for efficiency.
6973
*/
7074
public open fun isDispatchNeeded(context: CoroutineContext): Boolean = true
7175

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2016-2017 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package kotlinx.coroutines.experimental
18+
19+
import kotlinx.coroutines.experimental.intrinsics.startCoroutineUndispatched
20+
import kotlin.coroutines.experimental.Continuation
21+
import kotlin.coroutines.experimental.startCoroutine
22+
23+
/**
24+
* Defines start option for coroutines builders.
25+
* It is used in `start` parameter of [launch], [async], and [actor][kotlinx.coroutines.experimental.channels.actor]
26+
* coroutine builder functions.
27+
*/
28+
public enum class CoroutineStart {
29+
/**
30+
* Default -- schedules coroutine for execution according to its context.
31+
*
32+
* If the [CoroutineDispatcher] of the coroutine context returns `true` from [CoroutineDispatcher.isDispatchNeeded]
33+
* function as most dispatchers do, then the coroutine code is dispatched for execution later, while the code that
34+
* invoked the coroutine builder continues execution.
35+
*
36+
* Note, that [Unconfined] dispatcher always returns `false` from its [CoroutineDispatcher.isDispatchNeeded]
37+
* function, so starting coroutine with [Unconfined] dispatcher by [DEFAULT] is the same as using [UNDISPATCHED].
38+
*/
39+
DEFAULT,
40+
41+
/**
42+
* Starts coroutine lazily, only when it is needed.
43+
*
44+
* See the documentation for the corresponding coroutine builders for details:
45+
* [launch], [async], and [actor][kotlinx.coroutines.experimental.channels.actor].
46+
*/
47+
LAZY,
48+
49+
/**
50+
* Immediately executes coroutine until its first suspension point _in the current thread_ as if it the
51+
* coroutine was started using [Unconfined] dispatcher. However, when coroutine is resumed from suspension
52+
* it is dispatched according to the [CoroutineDispatcher] in its context.
53+
*/
54+
UNDISPATCHED;
55+
56+
/**
57+
* Starts the corresponding block as a coroutine with this coroutine start strategy.
58+
*
59+
* * [DEFAULT] uses [startCoroutine].
60+
* * [UNDISPATCHED] uses [startCoroutineUndispatched].
61+
* * [LAZY] does nothing.
62+
*/
63+
public operator fun <R, T> invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>) =
64+
when (this) {
65+
CoroutineStart.DEFAULT -> block.startCoroutine(receiver, completion)
66+
CoroutineStart.UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
67+
CoroutineStart.LAZY -> Unit // will start lazily
68+
}
69+
70+
/**
71+
* Returns `true` when [LAZY].
72+
*/
73+
public val isLazy: Boolean get() = this === LAZY
74+
}

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

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import kotlin.coroutines.experimental.startCoroutine
4141
* Usually, a deferred value is created in _active_ state (it is created and started), so its only visible
4242
* states are _active_ and _completed_ (_resolved_, _failed_, or _cancelled_) state.
4343
* However, [async] coroutine builder has an optional `start` parameter that creates a deferred value in _new_ state
44-
* when this parameter is set to `false`.
44+
* when this parameter is set to [CoroutineStart.LAZY].
4545
* Such a deferred can be be made _active_ by invoking [start], [join], or [await].
4646
*/
4747
public interface Deferred<out T> : Job {
@@ -105,29 +105,43 @@ public interface Deferred<out T> : Job {
105105
* The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
106106
* in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
107107
*
108-
* An optional [start] parameter can be set to `false` to start coroutine _lazily_. When `start = false`,
108+
* By default, the coroutine is immediately started.
109+
* An optional [start] parameter can be set to [CoroutineStart.LAZY] to start coroutine _lazily_. In this case,,
109110
* the resulting [Deferred] is created in _new_ state. It can be explicitly started with [start][Job.start]
110111
* function and will be started implicitly on the first invocation of [join][Job.join] or [await][Deferred.await].
111112
*
112-
* By default, the coroutine is immediately started. Set an optional [start] parameters to `false`
113-
* to create coroutine without starting it. In this case it will be _lazy_ and will start
113+
* @param context context of the coroutine
114+
* @param start coroutine start option
115+
* @param block the coroutine code
114116
*/
115-
public fun <T> async(context: CoroutineContext, start: Boolean = true, block: suspend CoroutineScope.() -> T) : Deferred<T> {
117+
public fun <T> async(
118+
context: CoroutineContext,
119+
start: CoroutineStart = CoroutineStart.DEFAULT,
120+
block: suspend CoroutineScope.() -> T
121+
): Deferred<T> {
116122
val newContext = newCoroutineContext(context)
117-
val coroutine = if (start)
118-
DeferredCoroutine<T>(newContext, active = true) else
119-
LazyDeferredCoroutine(newContext, block)
123+
val coroutine = if (start.isLazy)
124+
LazyDeferredCoroutine(newContext, block) else
125+
DeferredCoroutine<T>(newContext, active = true)
120126
coroutine.initParentJob(context[Job])
121-
if (start) block.startCoroutine(coroutine, coroutine)
127+
start(block, coroutine, coroutine)
122128
return coroutine
123129
}
124130

131+
/**
132+
* @suppress **Deprecated**: Use `start = CoroutineStart.XXX` parameter
133+
*/
134+
@Deprecated(message = "Use `start = CoroutineStart.XXX` parameter",
135+
replaceWith = ReplaceWith("async(context, if (start) CoroutineStart.DEFAULT else CoroutineStart.LAZY, block)"))
136+
public fun <T> async(context: CoroutineContext, start: Boolean, block: suspend CoroutineScope.() -> T): Deferred<T> =
137+
async(context, if (start) CoroutineStart.DEFAULT else CoroutineStart.LAZY, block)
138+
125139
/**
126140
* @suppress **Deprecated**: `defer` was renamed to `async`.
127141
*/
128142
@Deprecated(message = "`defer` was renamed to `async`", level = DeprecationLevel.WARNING,
129143
replaceWith = ReplaceWith("async(context, block = block)"))
130-
public fun <T> defer(context: CoroutineContext, block: suspend CoroutineScope.() -> T) : Deferred<T> =
144+
public fun <T> defer(context: CoroutineContext, block: suspend CoroutineScope.() -> T): Deferred<T> =
131145
async(context, block = block)
132146

133147
private open class DeferredCoroutine<T>(

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import kotlin.coroutines.experimental.startCoroutine
4545
*
4646
* Usually, a job is created in _active_ state (it is created and started), so its only visible
4747
* states are _active_ and _completed_. However, coroutine builders that provide an optional `start` parameter
48-
* create a coroutine in _new_ state when this parameter is set to `false`. Such a job can
48+
* create a coroutine in _new_ state when this parameter is set to [CoroutineStart.LAZY]. Such a job can
4949
* be made _active_ by invoking [start] or [join].
5050
*
5151
* A job in the coroutine [context][CoroutineScope.context] represents the coroutine itself.

kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Actor.kt

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,8 @@
1616

1717
package kotlinx.coroutines.experimental.channels
1818

19-
import kotlinx.coroutines.experimental.CoroutineDispatcher
20-
import kotlinx.coroutines.experimental.CoroutineScope
21-
import kotlinx.coroutines.experimental.Job
22-
import kotlinx.coroutines.experimental.newCoroutineContext
19+
import kotlinx.coroutines.experimental.*
20+
import kotlinx.coroutines.experimental.selects.SelectInstance
2321
import kotlin.coroutines.experimental.CoroutineContext
2422
import kotlin.coroutines.experimental.startCoroutine
2523

@@ -65,26 +63,68 @@ public interface ActorJob<in E> : Job, SendChannel<E> {
6563
* The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
6664
* in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
6765
*
66+
* By default, the coroutine is immediately started.
67+
* An optional [start] parameter can be set to [CoroutineStart.LAZY] to start coroutine _lazily_. In this case,
68+
* the coroutine [Job] is created in _new_ state. It can be explicitly started with [start][Job.start] function
69+
* and will be started implicitly on the first invocation of [join][Job.join] or on a first message
70+
* [sent][SendChannel.send] to this coroutine's mailbox channel.
71+
*
6872
* Uncaught exceptions in this coroutine close the channel with this exception as a cause and
6973
* the resulting channel becomes _failed_, so that any attempt to send to such a channel throws exception.
7074
*
7175
* See [newCoroutineContext] for a description of debugging facilities that are available for newly created coroutine.
7276
*
7377
* @param context context of the coroutine
7478
* @param capacity capacity of the channel's buffer (no buffer by default)
79+
* @param start coroutine start option
7580
* @param block the coroutine code
7681
*/
7782
public fun <E> actor(
7883
context: CoroutineContext,
7984
capacity: Int = 0,
85+
start: CoroutineStart = CoroutineStart.DEFAULT,
8086
block: suspend ActorScope<E>.() -> Unit
8187
): ActorJob<E> {
88+
val newContext = newCoroutineContext(context)
8289
val channel = Channel<E>(capacity)
83-
return ActorCoroutine(newCoroutineContext(context), channel).apply {
84-
initParentJob(context[Job])
90+
val coroutine = if (start.isLazy)
91+
LazyActorCoroutine(newContext, channel, block) else
92+
ActorCoroutine(newContext, channel, active = true)
93+
coroutine.initParentJob(context[Job])
94+
start(block, coroutine, coroutine)
95+
return coroutine
96+
}
97+
98+
private open class ActorCoroutine<E>(
99+
parentContext: CoroutineContext,
100+
channel: Channel<E>,
101+
active: Boolean
102+
) : ChannelCoroutine<E>(parentContext, channel, active), ActorScope<E>, ActorJob<E>
103+
104+
private class LazyActorCoroutine<E>(
105+
parentContext: CoroutineContext,
106+
channel: Channel<E>,
107+
private val block: suspend ActorScope<E>.() -> Unit
108+
) : ActorCoroutine<E>(parentContext, channel, active = false) {
109+
override val channel: Channel<E> get() = this
110+
111+
override fun onStart() {
85112
block.startCoroutine(this, this)
86113
}
114+
115+
suspend override fun send(element: E) {
116+
start()
117+
return super.send(element)
118+
}
119+
120+
override fun offer(element: E): Boolean {
121+
start()
122+
return super.offer(element)
123+
}
124+
125+
override fun <R> registerSelectSend(select: SelectInstance<R>, element: E, block: suspend () -> R) {
126+
start()
127+
return super.registerSelectSend(select, element, block)
128+
}
87129
}
88130

89-
private class ActorCoroutine<E>(parentContext: CoroutineContext, channel: Channel<E>) :
90-
ChannelCoroutine<E>(parentContext, channel), ActorScope<E>, ActorJob<E>

kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/ChannelCoroutine.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ import kotlin.coroutines.experimental.CoroutineContext
2323

2424
internal open class ChannelCoroutine<E>(
2525
override val parentContext: CoroutineContext,
26-
val channel: Channel<E>
27-
) : AbstractCoroutine<Unit>(active = true), Channel<E> by channel {
26+
open val channel: Channel<E>,
27+
active: Boolean
28+
) : AbstractCoroutine<Unit>(active), Channel<E> by channel {
2829
override fun afterCompletion(state: Any?, mode: Int) {
2930
val cause = (state as? JobSupport.CompletedExceptionally)?.cause
3031
if (!channel.close(cause) && cause != null)

kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Produce.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,4 @@ public fun <E> buildChannel(
110110
produce(context, capacity, block)
111111

112112
private class ProducerCoroutine<E>(parentContext: CoroutineContext, channel: Channel<E>) :
113-
ChannelCoroutine<E>(parentContext, channel), ProducerScope<E>, ProducerJob<E>
113+
ChannelCoroutine<E>(parentContext, channel, active = true), ProducerScope<E>, ProducerJob<E>

0 commit comments

Comments
 (0)