CoroutineScope & job.cancel() issue

#1

Hi, I’m trying following

class MyFragment : Fragment, CoroutineScope {
    private lateinit var job: Job
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        job = Job()
    }

    override fun onDestroy() {
        job.cancel()
        super.onDestroy()
    }

    private fun setState(state: State) {
        job.cancel()
        launch {
            Log.d("MyFragment", "before delay")
            delay(500)
            Log.d("MyFragment", "after delay")
            // do something with state
        }
    }
}

setState() can be called again before delay completes thats why i need to cancel previous coroutine.

Problem is that after calling job.cancel() in setState() results into launch {} never getting called (no log prints e.t.c.)

If i replace job.cancel() with job.cancelChildren() in setState, and job.cancel() in onDestroy everything works fine.

Hope to get some clarification on this issue and how to properly cancel.

Thanks

1 Like
#2

fun Job.cancelChildren(): Unit (source)

Cancels all children jobs of this coroutine using Job.cancel for all of them. Unlike Job.cancel on this job as a whole, the state of this job itself is not affected.

https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel-children.html

If you cancel the job then you have cancelled the whole coroutine scope, so launching a new task in that scope does not work, use this only in onDestroy.
If you cancel children then you scope remain alive, so you can launch new tasks.

You can consider to keep a reference for the single setState job and cancel only it.

    private var setStateJob: Job? = null

    private fun setState(state: State) {
        setStateJob?.cancel() // cancelAndJoin ?
        setStateJob = launch {
            Log.d("MyFragment", "before delay")
            delay(500)
            Log.d("MyFragment", "after delay")
            // do something with state
        }
    }
#3

i have called job.cancel() in onDestroyView and then e.g. orientation change will cause job to be canceled but somehow it still keeps working fine.

problem happens if job.cancel() is called before atleast once launch {} is called

keeping a reference for the single setState job works but then i guess it looses whole point of inheriting from CoroutineScope if i need to keep reference to every single job i create in this fragment

#4

No, this is not the point.
You don’t have to do it, you can do it, it depends by your software.

This is by design, your code rewritten as example.

import kotlinx.coroutines.*

fun main() {
    runBlocking {
        val job = Job()
        withContext(job) {
            launch { println("Works") }.join()
            job.cancel()
            launch { println("Error: job cancelled") }.join()
        }
    }
}