@@ -30,14 +30,15 @@ import kotlinx.coroutines.experimental.selects.select
3030import java.util.concurrent.Future
3131import kotlin.coroutines.experimental.Continuation
3232import kotlin.coroutines.experimental.CoroutineContext
33+ import kotlin.coroutines.experimental.buildSequence
3334import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn
3435
3536// --------------- core job interfaces ---------------
3637
3738/* *
3839 * A background job. Conceptually, a job is a cancellable thing with a simple life-cycle that
3940 * culminates in its completion. Jobs can be arranged into parent-child hierarchies where cancellation
40- * or completion of parent immediately cancels all its children.
41+ * or completion of parent immediately cancels all its [ children] .
4142 *
4243 * The most basic instances of [Job] are created with [launch] coroutine builder or with a
4344 * `Job()` factory function. Other coroutine builders and primitives like
@@ -60,7 +61,7 @@ import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn
6061 *
6162 * A job can be _cancelled_ at any time with [cancel] function that forces it to transition to
6263 * _cancelling_ state immediately. Job that is not backed by a coroutine and does not have
63- * children becomes _cancelled_ on [cancel] immediately.
64+ * [ children] becomes _cancelled_ on [cancel] immediately.
6465 * Otherwise, job becomes _cancelled_ when it finishes executing its code and
6566 * when all its children [complete][isCompleted].
6667 *
@@ -117,15 +118,15 @@ public interface Job : CoroutineContext.Element {
117118
118119 /* *
119120 * Returns `true` when this job is active -- it was already started and has not completed or cancelled yet.
120- * The job that is waiting for its children to complete is still considered to be active if it
121+ * The job that is waiting for its [ children] to complete is still considered to be active if it
121122 * was not cancelled.
122123 */
123124 public val isActive: Boolean
124125
125126 /* *
126127 * Returns `true` when this job has completed for any reason. A job that was cancelled and has
127128 * finished its execution is also considered complete. Job becomes complete only after
128- * all its children complete.
129+ * all its [ children] complete.
129130 */
130131 public val isCompleted: Boolean
131132
@@ -180,6 +181,25 @@ public interface Job : CoroutineContext.Element {
180181
181182 // ------------ parent-child ------------
182183
184+ /* *
185+ * Returns a sequence of this job's children.
186+ *
187+ * A job becomes a child of this job when it is constructed with this job in its
188+ * [CoroutineContext] or using an explicit `parent` parameter.
189+ *
190+ * A parent-child relation has the following effect:
191+ *
192+ * * Cancellation of parent with [cancel] or its exceptional completion (failure)
193+ * immediately cancels all its children.
194+ * * Parent cannot complete until all its children are complete. Parent waits for all its children to
195+ * complete in _completing_ or _cancelling_ state.
196+ * * Uncaught exception in a child, by default, cancels parent. In particular, this applies to
197+ * children created with [launch] coroutine builder. Note, that [async] and other future-like
198+ * coroutine builders do not have uncaught exceptions by definition, since all their exceptions are
199+ * caught and are encapsulated in their result.
200+ */
201+ public val children: Sequence <Job >
202+
183203 /* *
184204 * Attaches child job so that this job becomes its parent and
185205 * returns a handle that should be used to detach it.
@@ -205,7 +225,9 @@ public interface Job : CoroutineContext.Element {
205225 /* *
206226 * Cancels all children jobs of this coroutine with the given [cause]. Unlike [cancel],
207227 * the state of this job itself is not affected.
228+ * @suppress **Deprecated**: Binary compatibility, it is an extension now
208229 */
230+ @Deprecated(message = " Binary compatibility, it is an extension now" , level = DeprecationLevel .HIDDEN )
209231 public fun cancelChildren (cause : Throwable ? = null)
210232
211233 // ------------ state waiting ------------
@@ -385,6 +407,8 @@ public class JobCancellationException(
385407 (message!! .hashCode() * 31 + job.hashCode()) * 31 + (cause?.hashCode() ? : 0 )
386408}
387409
410+ // -------------------- Job extensions --------------------
411+
388412/* *
389413 * Unregisters a specified [registration] when this job is complete.
390414 *
@@ -440,6 +464,25 @@ public suspend fun Job.cancelAndJoin() {
440464 return join()
441465}
442466
467+ /* *
468+ * Cancels all [children][Job.children] jobs of this coroutine with the given [cause] using [cancel]
469+ * for all of them. Unlike [cancel] on this job as a whole, the state of this job itself is not affected.
470+ */
471+ public fun Job.cancelChildren (cause : Throwable ? = null) {
472+ children.forEach { it.cancel(cause) }
473+ }
474+
475+ /* *
476+ * Suspends coroutine until all [children][Job.children] of this job are complete using
477+ * [join] for all of them. Unlike [join] on this job as a whole, it does not wait until
478+ * this job is complete.
479+ */
480+ public suspend fun Job.joinChildren () {
481+ children.forEach { it.join() }
482+ }
483+
484+ // -------------------- CoroutineContext extensions --------------------
485+
443486/* *
444487 * Cancels [Job] of this context with an optional cancellation [cause]. The result is `true` if the job was
445488 * cancelled as a result of this invocation and `false` if there is no job in the context or if it was already
@@ -1057,19 +1100,21 @@ public open class JobSupport(active: Boolean) : Job, SelectClause0, SelectClause
10571100 }
10581101 }
10591102
1060- override fun attachChild (child : Job ): DisposableHandle =
1061- invokeOnCompletion(onCancelling = true , handler = Child (this , child))
1062-
1063- public override fun cancelChildren (cause : Throwable ? ) {
1064- val state = this .state
1103+ override val children: Sequence <Job > get() = buildSequence<Job > {
1104+ val state = this @JobSupport.state
10651105 when (state) {
1066- is Child -> state.childJob.cancel(cause)
1067- is Incomplete -> state.list?.cancelChildrenList(cause)
1106+ is Child -> yield (state.childJob)
1107+ is Incomplete -> state.list?.let { list ->
1108+ list.forEach<Child > { yield (it.childJob) }
1109+ }
10681110 }
10691111 }
10701112
1071- private fun NodeList.cancelChildrenList (cause : Throwable ? ) {
1072- forEach<Child > { it.childJob.cancel(cause) }
1113+ override fun attachChild (child : Job ): DisposableHandle =
1114+ invokeOnCompletion(onCancelling = true , handler = Child (this , child))
1115+
1116+ public override fun cancelChildren (cause : Throwable ? ) {
1117+ this .cancelChildren(cause) // use extension function
10731118 }
10741119
10751120 /* *
0 commit comments