kite
05/20/2025, 1:06 AM1. Why do we still need to explicitly callon a childcomplete()
, even when it's structurally connected to a parent viaJob
?Job(parent)
2. How do the internal implementations differ between using(which creates anlaunch
) and creating aAbstractCoroutine
directly, and how do those differences affect structured concurrency and job completion behavior?Job()
kite
05/20/2025, 1:06 AMJob()
and Job(parent: Job?)
, I learned that using a parent job ensures the child is part of the structured coroutine hierarchy. This means that if the parent is cancelled, the child job is also cancelled, maintaining structured concurrency.
However, I found something confusing.
Even when a child job is properly linked to a parent using Job(parent)
, we still need to call complete()
explicitly to mark it as finished. I would have expected the structured relationship to automatically handle this. Is there a specific reason behind this design? Why doesn't the completion state propagate implicitly?
Additionally, I noticed that when using launch
, it internally creates an AbstractCoroutine
instance, which manages its own job and lifecycle. In contrast, using Job()
alone does not create a coroutine—only a job object. I'm wondering how this implementation difference impacts job behavior and structured concurrency.
If anyone could help clarify the reasoning or intent behind this design, I would really appreciate it.
Thanks in advance!ephemient
05/20/2025, 1:33 AMThis function transitions this job into completed state if it was not completed or cancelled yet. However, that if this job has children, then it transitions into completing state and becomes complete once all its children are complete. See Job for details.
kite
05/20/2025, 1:44 AMcomplete()
on a child created with Job(parent)
, even though it's structurally linked to the parent?
Is this to separate manual job management from coroutine builders? Would love to understand the intent.ephemient
05/20/2025, 2:55 AMcoroutineScope {
launch {
delay(1000)
// child will take a while to complete
}
// parent is completing, but must not return until the child completes
}
Sam
05/20/2025, 7:45 AMlaunch
implements its Job
as an AbstractCoroutine
object, conceptually I think it's more helpful to think of the job and the coroutine as two separate things.
• A coroutine builder makes a coroutine and a job. The coroutine uses the job to record its outcome: when the coroutine finishes running its code, it marks the job as complete (or at least completing).
• The Job()
function just makes a job with no coroutine. Without an associated coroutine, a job will never be automatically marked as complete; you would always need to complete it manually.
To understand why completion doesn't propagate down the job hierarchy in the same way that cancellation does, I find it helpful to think of a job as a communication mechanism between two different parties—a producer (usually the coroutine that owns the job) and a consumer.
• The cancel()
function lets a consumer tell the producer to stop doing work.
• The complete()
function lets the producer tell the consumer that it has finished doing work.
To complete a task, you have to wait for that task and all of its subtasks—you can't just decide that the subtasks are complete because the parent task said so. The subtasks are still responsible for doing their own work and determining their own completion. On the other hand, to cancel a task, you have to cancel that task and all of its subtasks—so the child tasks should be cancelled when the parent task is cancelled.kite
05/22/2025, 2:40 PM