Hey, I'm launching a long-running coroutine in som...
# coroutines
j
Hey, I'm launching a long-running coroutine in some scope. Is there a way to obtain its
Job
from my scope later (e.g by marking it in some way)?
I tried creating a wrapper class
class JobMarker(parent: Job?) : Job by Job(parent)
, but that doesn't register JobMarker as child and thus cannot be retrieved
d
launch
allows you to pass in your own
CoroutineContext
. You should be able to put in your own
CoroutineContext.Key
and then obtain it again later.
j
that seems nice, but how can I retreive this child context from parent scope?
d
The "parent scope" is a
CoroutineScope
, right? If so, get it's
Job
(every
CoroutineScope
should have one!):
scope.coroutineContext[Job]
. Then search through that Job's children using
job.children
j
yeah, but how to select one specific
Job
from all children?
d
You have to find it, by checking if it has your marker key (
CoroutineContext.Key
Notice that
Job
is itself a
CoroutineContext
, so it will have your key in it
z
launch
returns the
Job
of the child, why can’t you just store that? It would be a lot simpler than writing all this search logic.
j
I didn't notice that job itself is a
CoroutineContext
, thanks
@Zach Klippenstein (he/him) [MOD] sure, but I am wondering if there's another way
because that would be duplicating something that is already stored in CoroutineContext
z
Parents needing references to their child jobs is a common use case – that’s presumably the whole reason why the
launch
API was designed the way it was, to return the child’s
Job
. Using the existing API means you need to write less code (fewer potential bugs) and makes your code’s intent much more obvious than going out of your way to write a bunch of search logic just to avoid creating a variable.
j
i won't disagree with that, i'm just looking for spicier way to do it
@Zach Klippenstein (he/him) [MOD] here's funny thing, this actually doesn't work and I have no idea why
Copy code
class JobMarker(val id: String) : CoroutineContext.Element {
    override val key = Key

    companion object Key : CoroutineContext.Key<JobMarker>
}

fun main() = runBlocking {
    launch(JobMarker("a")) { suspendCoroutine {  } }

    println(coroutineContext[Job]!!.children.first { it[JobMarker]?.id == "a" })
}
this will actually throw an error due to no JobMarker key
z
Job.children
returns a list of the job’s child jobs, not their complete contexts.
CoroutineContext
is basically just a strongly-typed
Map<Key, Element>
, where the types of each entry’s keys and values are linked. When you pass a
CoroutineContext
to
launch
, you’re saying “take the current context, add all the entries from the new context to it (existing keys are “overwritten”), and use that as the context for the new coroutine”. So in your example, the child coroutine you launch has a context that looks something like this:
Copy code
mapOf(
  Job.Key to Job(parent = coroutineContext[Job]),
  JobMarker.Key to JobMarker("a")
)
(there’s some other stuff too, like the dispatcher, but that’s not relevant here) Jobs form a hierarchy, but that hierarchy only includes the jobs themselves, not the entire context. I think the reason for the confusion is that it looks a bit strange that a
Job
“is a”
CoroutineContext
. This doesn’t mean the
Job
contains the whole context, it’s just how the api works, so that, for example, you can pass an instance of
Job
to a function that takes a context and not need a bunch of
job.asCoroutineContext
methods everywhere, and so that the
+
operator can be defined for all of them. All `Job`s are `CoroutineContext`s, not all `CoroutineContext`s are `Job`s.
j
thanks, that explains a lot
to sum up, there's no way to access "child" contexts from "parent" context because there's no such relation?
z
As far as I know, yes.
That said, if you step through your code in the debugger, you might notice that your child jobs are instances of
StandaloneCoroutine
, which is a private class in the coroutines library that both implements
Job
and has a reference to the current and parent `CoroutineContext`s. This can be useful for debugging, but I would be extremely wary of trying to access this stuff programmatically via reflection or something, since it’s a private implementation detail and could change at any time without notice.
j
thanks