协程作用域被取消后,它还能再次使用吗



当我们有一个协程作用域时,当它被取消时,它可以再次使用吗?

例如,对于以下情况,当我有scope.cancel时,scope.launch不再工作

@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")
}
}

类似地,当我们在调用await之前有scope.cancel时,

@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")
}
}

它不会执行。相反,它将与崩溃

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)

取消的协程作用域不能再用于launchasync,这是真的吗?

您可能希望使用底层CoroutineContext及其cancelChildren()方法,而不是使用CoroutineScope来取消其中所有已启动的作业,该方法不会影响Job状态(对于普通cancel()方法来说不是这样(,并允许在调用后继续启动新的协程。

跟进@Alex Bonel的回应。

例如,您有一种类似的方法

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

您可以一次又一次地调用doApiCall()

viewModelScope.coroutineContext.cancelChildren()
doApiCall() 

调用doApiCall()不会有任何效果。

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

我在compose中使用了它,它略有不同。

  1. 在compose中定义协同程序范围

val coroutineScope = rememberCoroutineScope()

  1. 启动协同程序

coroutineScope.launch(Dispatchers.Main) { //Perform your operation }

  1. 取消所有属于协同程序作用域的协同程序

coroutineScope.coroutineContext.cancelChildren()

与上面的lifecycleScope评论相关,我不确定这样的取消在实践中有多常见?fwiw类似以下内容将起作用:

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")
}

此特定示例将打印

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

我只需要收集一次事件,然后停止侦听。我想出了这个解决方案:

/**
* Collect the value once and stop listening (one-time events)
*/
suspend fun <T> Flow<T>.collectOnce(action: suspend (value: T) -> Unit) {
try {
coroutineScope {
collectLatest {
action(it)
this@coroutineScope.cancel()
}
}
} catch (e: CancellationException) { }
}

你可以这样使用它:

viewModelScope.launch {
myStateFlow.collectOnce {
// Code to run
}
}

最新更新