@@ -30,14 +30,15 @@ import kotlinx.coroutines.experimental.selects.select
30
30
import java.util.concurrent.Future
31
31
import kotlin.coroutines.experimental.Continuation
32
32
import kotlin.coroutines.experimental.CoroutineContext
33
+ import kotlin.coroutines.experimental.buildSequence
33
34
import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn
34
35
35
36
// --------------- core job interfaces ---------------
36
37
37
38
/* *
38
39
* A background job. Conceptually, a job is a cancellable thing with a simple life-cycle that
39
40
* 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] .
41
42
*
42
43
* The most basic instances of [Job] are created with [launch] coroutine builder or with a
43
44
* `Job()` factory function. Other coroutine builders and primitives like
@@ -60,7 +61,7 @@ import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn
60
61
*
61
62
* A job can be _cancelled_ at any time with [cancel] function that forces it to transition to
62
63
* _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.
64
65
* Otherwise, job becomes _cancelled_ when it finishes executing its code and
65
66
* when all its children [complete][isCompleted].
66
67
*
@@ -117,15 +118,15 @@ public interface Job : CoroutineContext.Element {
117
118
118
119
/* *
119
120
* 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
121
122
* was not cancelled.
122
123
*/
123
124
public val isActive: Boolean
124
125
125
126
/* *
126
127
* Returns `true` when this job has completed for any reason. A job that was cancelled and has
127
128
* finished its execution is also considered complete. Job becomes complete only after
128
- * all its children complete.
129
+ * all its [ children] complete.
129
130
*/
130
131
public val isCompleted: Boolean
131
132
@@ -180,6 +181,25 @@ public interface Job : CoroutineContext.Element {
180
181
181
182
// ------------ parent-child ------------
182
183
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
+
183
203
/* *
184
204
* Attaches child job so that this job becomes its parent and
185
205
* returns a handle that should be used to detach it.
@@ -205,7 +225,9 @@ public interface Job : CoroutineContext.Element {
205
225
/* *
206
226
* Cancels all children jobs of this coroutine with the given [cause]. Unlike [cancel],
207
227
* the state of this job itself is not affected.
228
+ * @suppress **Deprecated**: Binary compatibility, it is an extension now
208
229
*/
230
+ @Deprecated(message = " Binary compatibility, it is an extension now" , level = DeprecationLevel .HIDDEN )
209
231
public fun cancelChildren (cause : Throwable ? = null)
210
232
211
233
// ------------ state waiting ------------
@@ -385,6 +407,8 @@ public class JobCancellationException(
385
407
(message!! .hashCode() * 31 + job.hashCode()) * 31 + (cause?.hashCode() ? : 0 )
386
408
}
387
409
410
+ // -------------------- Job extensions --------------------
411
+
388
412
/* *
389
413
* Unregisters a specified [registration] when this job is complete.
390
414
*
@@ -440,6 +464,25 @@ public suspend fun Job.cancelAndJoin() {
440
464
return join()
441
465
}
442
466
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
+
443
486
/* *
444
487
* Cancels [Job] of this context with an optional cancellation [cause]. The result is `true` if the job was
445
488
* 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
1057
1100
}
1058
1101
}
1059
1102
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
1065
1105
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
+ }
1068
1110
}
1069
1111
}
1070
1112
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
1073
1118
}
1074
1119
1075
1120
/* *
0 commit comments