当我们有一个协程作用域时,当它被取消时,它可以再次使用吗?
例如,对于以下情况,当我有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)
取消的协程作用域不能再用于launch
或async
,这是真的吗?
您可能希望使用底层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中使用了它,它略有不同。
- 在compose中定义协同程序范围
val coroutineScope = rememberCoroutineScope()
- 启动协同程序
coroutineScope.launch(Dispatchers.Main) { //Perform your operation }
- 取消所有属于协同程序作用域的协同程序
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
}
}