'After a coroutine scope is cancel, can it still be used again?

When we have a coroutine scope, when it is canceled, can it be used again?

e.g. for the below, when I have scope.cancel, the scope.launch no longer work

    @Test
    fun testingLaunch() {
        val scope = MainScope()
        runBlocking {
            scope.cancel()
            scope.launch {
                try {
                    println("Start Launch 2")
                    delay(200)
                    println("End Launch 2")
                } catch (e: CancellationException) {
                    println("Cancellation Exception")
                }
            }.join()

            println("Finished")
        }
    }

Similarly, when we have scope.cancel before await called,

    @Test
    fun testingAsync() {
        val scope = MainScope()
        runBlocking {
            scope.cancel()
            val defer = scope.async {
                try {
                    println("Start Launch 2")
                    delay(200)
                    println("End Launch 2")
                } catch (e: CancellationException) {
                    println("Cancellation Exception")
                }
            }
            defer.await()
            println("Finished")
        }
    }

It will not execute. Instead, it will crash with

kotlinx.coroutines.JobCancellationException: Job was cancelled
; job=SupervisorJobImpl{Cancelled}@39529185
    at kotlinx.coroutines.JobSupport.cancel(JobSupport.kt:1579)
    at kotlinx.coroutines.CoroutineScopeKt.cancel(CoroutineScope.kt:217)
    at kotlinx.coroutines.CoroutineScopeKt.cancel$default(CoroutineScope.kt:215)
    at com.example.coroutinerevise.CoroutineExperiment$testingAsync$1.invokeSuspend(CoroutineExperiment.kt:241)
    at |b|b|b(Coroutine boundary.|b(|b)
    at kotlinx.coroutines.DeferredCoroutine.await$suspendImpl(Builders.common.kt:101)
    at com.example.coroutinerevise.CoroutineExperiment$testingAsync$1.invokeSuspend(CoroutineExperiment.kt:254)
Caused by: kotlinx.coroutines.JobCancellationException: Job was cancelled; job=SupervisorJobImpl{Cancelled}@39529185
    at kotlinx.coroutines.JobSupport.cancel(JobSupport.kt:1579)
    at kotlinx.coroutines.CoroutineScopeKt.cancel(CoroutineScope.kt:217)
    at kotlinx.coroutines.CoroutineScopeKt.cancel$default(CoroutineScope.kt:215)
    at com.example.coroutinerevise.CoroutineExperiment$testingAsync$1.invokeSuspend(CoroutineExperiment.kt:241)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)

Is it true, a canceled coroutine scope cannot be used for launch or async anymore?



Solution 1:[1]

Instead of using CoroutineScope for cancelling all of the launched jobs in it, you may want to use the underlying CoroutineContext with its cancelChildren() method which doesn't affect the Job state (which is not true for plain cancel() method) and allows to continue launching new coroutines after being invoked.

Solution 2:[2]

Following up on @Alex Bonel response.

For example you have a method like

fun doApiCall() {
 viewModelScope.launch { 
     // do api call here
 }
}
  

You can call doApiCall() again and again

viewModelScope.coroutineContext.cancelChildren()
doApiCall() 

Calling doApiCall() will not have any effect.

viewModelScope.coroutineContext.cancel()
doApiCall() // would not call 

Solution 3:[3]

Related to lifecycleScope comment above, I'm not sure how common cancellation like this would be in practice? fwiw something like following will work:

val scope = MainScope()
runBlocking {
    val job1 = scope.launch {
        try {
            println("Start Launch 1")
            delay(200)
            println("End Launch 1")
        } catch (e: CancellationException) {
            println("Cancellation Exception")
        }
    }

    job1.cancel()
    val job2 = scope.launch {
        try {
            println("Start Launch 2")
            delay(200)
            println("End Launch 2")
        } catch (e: CancellationException) {
            println("Cancellation Exception")
        }
    }
    job2.join()

    println("Finished")
}

This particular example will print

Start Launch 1
Cancellation Exception
Start Launch 2
End Launch 2
Finished

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Pang
Solution 2 kuzdu
Solution 3 John O'Reilly