'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 |