c-an
c-an

Reputation: 4090

What does CoroutineContext do?

I created Custom Continuation like this

class LogContinuation<T>(
    private val cont: Continuation<T>
) : Continuation<T> {
    override val context: CoroutineContext
        get() = cont.context
    override fun resumeWith(result: Result<T>) {
        println("Log: LogContinuation resume with thread: ${Thread.currentThread().name}")
        cont.resumeWith(result)
    }
}

And custom interceptor

val myInterceptor = object: ContinuationInterceptor{
    override val key: CoroutineContext.Key<*>
        get() = ContinuationInterceptor.Key

    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
        println("3 interceptContinuation: ${continuation.context[CoroutineName].toString()}")
        return LogContinuation(continuation)
    }

    override fun releaseInterceptedContinuation(continuation: Continuation<*>) {
        println("4 releaseInterceptedContinuation: ${continuation.context[CoroutineName].toString()}")
    }
}

val myInterceptor2 = object: ContinuationInterceptor{
    override val key: CoroutineContext.Key<*>
        get() = ContinuationInterceptor.Key

    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
        println("5 interceptContinuation: ${continuation.context[CoroutineName].toString()}")
        return Log2Continuation(continuation)
    }

    override fun releaseInterceptedContinuation(continuation: Continuation<*>) {
        println("6 releaseInterceptedContinuation: ${continuation.context[CoroutineName].toString()}")
    }
}

And I tried this:

fun main(){
    runBlocking(context = myInterceptor + CoroutineName("myRunBlocking") + myInterceptor2 + CoroutineName("myRunBlocking2")){
        println("1 runBlocking start: ${Thread.currentThread().name}")
        delay(1000L)
        println("2 runBlocking end: ${Thread.currentThread().name}")
        repeat(1){
            println("${7+it} runBlocking start: ${Thread.currentThread().name}")
            println("${8+it} runBlocking end: ${Thread.currentThread().name}")
        }
    }
}

And I get

5 interceptContinuation: CoroutineName(myRunBlocking2)
Log: LogContinuation resume with thread: main
1 runBlocking start: main
Log: LogContinuation resume with thread: kotlinx.coroutines.DefaultExecutor
2 runBlocking end: kotlinx.coroutines.DefaultExecutor
7 runBlocking start: kotlinx.coroutines.DefaultExecutor
8 runBlocking end: kotlinx.coroutines.DefaultExecutor
6 releaseInterceptedContinuation: CoroutineName(myRunBlocking2)

What's happening here? I thought scope can pass only CoroutineContext but, CoroutineIntercepter and CoroutineName can be passed. Those are different types but how can it be done?

Upvotes: 0

Views: 571

Answers (1)

broot
broot

Reputation: 28362

CoroutineContext is basically a map of CoroutineContext.Element items. To make the API a little smoother, coroutines authors introduced a smart trick. Each Element is itself a CoroutineContext of this single item.

You can verify that both CoroutineName and ContinuationInterceptor are actually subtypes of CoroutineContext.

As an example, let's get your code:

myInterceptor + CoroutineName("myRunBlocking") + myInterceptor2 + CoroutineName("myRunBlocking2")

This could be understood as:

// not a true Kotlin code
coroutineContextOf(myInterceptor) +
  coroutineContextOf(CoroutineName("myRunBlocking")) +
  coroutineContextOf(myInterceptor2) +
  coroutineContextOf(CoroutineName("myRunBlocking2"))

Here you have 4 different coroutine contexts and you merge them by overwriting the same items with newer ones. As a result, you pass to runBlocking() something like:

// not a true Kotlin code
coroutineContextOf(myInterceptor2, CoroutineName("myRunBlocking2"))

Upvotes: 4

Related Questions