<@U2E974ELT> Is it possible to use the coroutine c...
# coroutines
s
@elizarov Is it possible to use the coroutine context to hold other information, such as a database connection that follows a request through the server? I can think of several instances where it would be nice to to use a coroutine context to intrinsically hold certain pieces of data to follow an async request.
g
Sure, coroutine context is created exactly for this, you can pass any information
s
How do I pass information into the coroutine context? Am I creating my own coroutine context class, and using
is
checks to pull data, or is there another way (I see there is a CoroutineContext.Key)? What about the same information being available in the child coroutine contexts as well?
g
Yes, you creating own coroutine context
and using
is
checks to pull data
No, you can just use Key for that and when you get request by Key it will be already casted to your context class
What about the same information being available in the child coroutine contexts as well?
The same as now, you can pass parent context explicitly, most of primitives just getting parent context and merge with some own, like
CoroutineScope
context will be used for all child coroutines or
withContext
merges passed context with parent and so on
@spierce7 See CoroutineName context implementation as a simple example of custom context (just 1 additional field)
And to retrieve it on any coroutine just call:
Copy code
val coroutineName = coroutineContext[CoroutineName]?.name
As you can see context is always nullable but casted for you because Key is also type information of context
d
What's the advantage over passing these things in from the outer scope? And also, what conceptual relationship would these things have to the coroutineContext, which as far as I understand is just for tracking coroutine state?
g
which as far as I understand is just for tracking coroutine state
this is not true, coroutineContext is for any coroutine related metadata
it’s like ThreadLocal
d
Practically speaking, what would be an example of a good use case?
g
I don’t think that some every day usage cases are suitable, but for framework/library writers it can be very useful in some cases
like in case of kotlinx.coroutines
d
Thanks for the ThreadLocal hint 😉, I started looking into it and found a few nice articles...
s
@gildor You end up helping me out a lot. Thanks for that. Do you work for JB?
I'm still not understanding. The following example fails. The actual type is a
CombinedContext
. Would I need to override some additional methods?
Copy code
@Test fun coroutineTest() = runBlocking {
    TestScope.launch {
        assertEquals(CoroutineName::class, coroutineContext::class)
    }.join()
}

object TestScope : CoroutineScope {
    override val coroutineContext: CoroutineContext
        get() = CoroutineName("Blah")
}

data class CoroutineName(
    val name: String
) : AbstractCoroutineContextElement(CoroutineName) {
    companion object Key : CoroutineContext.Key<CoroutineName>
    override fun toString(): String = "CoroutineName($name)"
}
g
No, I'm not working for JB or Kotlin team :)
Nono, you shouldn’t just check class of context, instead get context by key, because coroutines add own context items
The reason why you test is fails, because
coroutineContext
is not only your item, it’s merged with other default contexts, just add
println(coroutineContext)
in
launch
to check what is there, most obvious thing is Job, because you use launch
s
Ok, that works.
If I remove
companion object Key : CoroutineContext.Key<CoroutineName>
, it stops working. Is reflection being used to retrieve the key for the context? How does that work?
g
No, reflections are not used
Just something similar to map where Key is key of map, but also Key is parametrized by context type, so you getting context of correct type
s
But how does the coroutine context know which key to associate with a specific context?
If I remove
companion object Key : CoroutineContext.Key<CoroutineName>
from the
CoroutineName
class it no longer works
g
If I remove
companion object Key : CoroutineContext.Key<CoroutineName>
from the
CoroutineName
class it no longer works
Of course, because companion object used as a Key
see:
Copy code
coroutineContext[CoroutineName]
It’s actually the same as
Copy code
coroutineContext[CoroutineName.Key]
Just implicit reference to companion object
It’s pretty simple Map-like datastrcture
s
Right, but how does it retrieve the key for a context if not through reflection?
g
This is how kotlin companion object works
s
I understand that once the key is retrieved, it's stored in a map-like data structure
g
coroutineContext[CoroutineName]
CoroutineName is companion object
s
Please explain? I think it's possible there is something for me to learn here
g
see, imagine class:
class Foo
you cannot just use it as
Foo
, there is no such syntax in Kotlin, correct? Like
val foo = Foo
You will get compile error
s
right. You'd have to do Foo()
g
but, let’s now add companion object to it:
Copy code
class Foo {
    companion object
}
empty, without interface, without members
now this code is completely valid:
Copy code
val foo = Foo
and
foo
contains instance of companion object of Foo
s
I see... I didn't realize the key was a companion object
I just thought it was an object inside of it. What's the point of naming the companion object as type Key?
g
Actually name
Key
is redundand, it’s just a convention
s
ic
g
but what is important is parent interface
companion object : CoroutineContext.Key<CoroutineName>
you see, companion object implements interaface CoroutineContext.Key and has generic type that equals to your context type
s
right
g
so Key is key of this map-like context and has parameterized type so type check works on compile time, no reflections
s
So I should be able to do something like
CoroutineName is CoroutineContext.Key
and that should return true
g
yes, because
CoroutineName
is not a type, but companion object instance
s
ic
one more piece of the puzzle is missing for me
so for a given
coroutineContext
, how do I access it's companion object?
g
companion object of coroutineContext? what do you mean?
you just lookup required context by key
Copy code
coroutineContext[SomeCoroutineContextKey]
s
so there has to be a way to programmatically access the companion object for a class instance for this all to work without reflection
somehow the
SomeCoroutineContextKey
has to be stored inside of the
coroutineContext
right?
we never give it the key. We only give it the coroutine context, and so the coroutine library somehow grabs the Key
g
I see what you mean
check plus operator implementation
Your context should extend
AbstractCoroutineContextElement
, or just implement Element
if you want to retrieve it by Key
s
lookinmg
g
Element just has
key
property with key
so you can get key of context
s
ah, I see it now
public abstract class AbstractCoroutineContextElement(public override val key: Key<*>) : Element
the Key / companion object is fed into the constructor for
AbstractCoroutineContextElement
I have a full understanding of it now. Thanks 🙂
g
Yes, otherwise you have to implement get/fold yourself
s
awesome 🙂
thanks 🙂
g
You are welcome👍