https://kotlinlang.org logo
Title
j

Jemshit Iskenderov

03/16/2019, 6:30 AM
Hi, i have confusion when using
CoroutineScope.cancel()
and
CoroutineScope.job.cancel()
. Here is the CoroutineScope:
val scope = object : CoroutineScope {
        val parentJob = Job()
        override val coroutineContext: CoroutineContext
            get() = parentJob + <http://Dispatchers.IO|Dispatchers.IO> 
}
Observation 1: when Job of CoroutineScope is cancelled, both seemed not active:
scope.launch {
        withContext(Dispatchers.Default) {
            println("result")
        }
        scope.parentJob.cancel()
        println("isActive:$isActive, job.isActive:${scope.parentJob.isActive}")

        // PRINTS isActive:false, job.isActive:false
}
Observation 2: when CoroutineScope is cancelled, only CoroutineScope.isActive seems false. Job.isActive is true
scope.launch {
        withContext(Dispatchers.Default) {
            println("result")
        }
        cancel()
        println("isActive:$isActive, job.isActive:${scope.parentJob.isActive}")

        // PRINTS isActive:false, job.isActive:true
}
Observation 3 (CoroutineScope for Android Fragment):
protected lateinit var job: Job
override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job

launch {
    val result = withContext(Dispatchers.Default) {
          // ...
    }
    this@Fragment.job.cancel()
    // Log.d("$isActive ${this@Fragment.job.isActive}")
    // PRINTS true, false
}
Question: when looking at source code,
CoroutineScope.cancel
and
CoroutineScope.isActive
calls methods of its Job. But in above code,
CoroutineScope.cancel
does not return
Job.isActive
or vice versa. Why this behavior? Is
withContext
changing something?
d

Dico

03/16/2019, 7:20 AM
withContext
creates a child coroutine, its context contains that child as a
Job
. The cancellation of said
Job
is not exceptional, and thus the event is not propagated to the parent job from the coroutine scope that you declared.
I dont think this explains what you are seeing though. I think that's down to
Job
being threadsafe. Theres no guarantee that its cancellation completes before
cancel()
returns. It will go through at least 2 state changes before it is finally cancelled/dead.
j

Jemshit Iskenderov

03/16/2019, 12:08 PM
coroutineContext[Job]==scope.parentJob
returns
false
for some reason
d

Dico

03/16/2019, 12:21 PM
And I explained why that is the case in my first comment.
j

Jemshit Iskenderov

03/16/2019, 12:27 PM
You are saying
cancel
is called for/from
coroutineContext
of
withContext()
coroutine, thats why cancellation does not go to parent job, right? But i call
cancel()
inside
scope.launch{}
, also i directly cancel
parentJob
of scope.
This returns `true`:
println("${coroutineContext[Job] === scope.job.children.iterator().next()}")
. So this means, Job instance inside CoroutineScope is only parent of CoroutineScope.coroutineContext
d

Dico

03/16/2019, 12:30 PM
Yes.
j

Jemshit Iskenderov

03/16/2019, 12:32 PM
CoroutineScope.job.cancel():
cancels parent Job of that scope. Control is done via
if(CoroutineScope.job.isActive)
CoroutineScope.cancel():
does not cancel parent Job of that scope. (why does this exists?). Control is done via
if(CoroutineScope.isActive)
I understand the Observation 1 & 2. I don’t get why cancelling the most parentJob does not cancel job of
scope.launch{}
in Observation 3.
d

Dico

03/16/2019, 2:32 PM
The job in
scope.launch
cannot be inactive until the end of the coroutine's code block. It might be in
cancelling
state internally.
j

Jemshit Iskenderov

03/16/2019, 2:42 PM
Oh, that is confusing. Thanks