【问题标题】:After a coroutine scope is cancel, can it still be used again?协程作用域被取消后,还能再使用吗?
【发布时间】:2022-04-20 08:13:36
【问题描述】:

当我们有协程作用域的时候,当它被取消的时候,还能重新使用吗?

例如对于下面,当我有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

【问题讨论】:

  • 那种。因为CoroutineScope 只是CoroutineContext 的持有者,而Job 又必须包含Job。请参阅CoroutineScope() 函数实现,并注意如果在上下文中找不到Job,则会添加一个Job。当您在CoroutineScope 上调用cancel() 时,它正在取消内部Job。因此,要重用CoroutineScope,必须将新的Job 添加到上下文中。
  • 经过测试,AFAIK,您确实需要创建一个新的CoroutineScope,因为底层的CoroutineContext 无法更新。
  • 这很可悲。不确定这是否也适用于lifecycleScope
  • 我不认为lifecycleScope打算手动取消。它被称为“生命周期”,因为它的生命被自动管理以匹配生命周期。手动取消它会破坏它。

标签: kotlin kotlin-coroutines


【解决方案1】:

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

【讨论】:

  • 你能在代码中展示如何访问底层的 CoroutineContext 并使用它的 cancelChildren() 方法吗?
【解决方案2】:

跟进@Alex Bonel 的回复。

例如,你有一个类似的方法

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

您可以多次拨打doApiCall()

viewModelScope.coroutineContext.cancelChildren()
doApiCall() 

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

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

【讨论】:

    【解决方案3】:

    与上面的生命周期范围评论有关,我不确定在实践中这样的取消有多常见? 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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-07
      • 2019-07-16
      • 2013-02-19
      • 2021-05-17
      • 2013-05-13
      • 1970-01-01
      相关资源
      最近更新 更多