【问题标题】:How can I launch coroutine that doesn't hold the containing function from returning?如何启动不阻止包含函数返回的协程?
【发布时间】:2021-12-19 16:18:01
【问题描述】:

我有一个函数应该完成它的工作,但也会触发一个后台进程。触发的进程,即使在函数中启动,也不应该阻止它返回,因为它与正在完成的工作没有直接关系。

代码示例:

suspend fun coroutiny(): String {
 coroutineScope {
    launch(Dispatchers.IO) {
        delay(1000)
        println("Independent thing that should not avoid coroutiny to return")
     }
 }
 return "Coroutiny return"
}


suspend fun main() {
 println(coroutiny())
 println("after coroutiny")
 delay(2000)
}

在这种情况下我想要的结果是:

Coroutiny return
after coroutiny
Independent thing that should not avoid coroutiny to return

但我得到的是:

Independent thing that should not avoid coroutiny to return
Coroutiny return
after coroutiny

我知道会发生这种情况,因为 coroutineScope {...} 仅在内部的协程返回/完成时返回。 我需要帮助的是知道如何在不求助于 GlobalScope.launch {...} 的情况下做我想做的事,我想避免这种情况,因为我的用例与记录在案的 GlobalScope 可接受的用例不匹配。

【问题讨论】:

    标签: kotlin concurrency parallel-processing kotlin-coroutines


    【解决方案1】:

    正如您所说,coroutineScope 的方法旨在并行化您的挂起函数内的工作,但所有工作都应在您返回后完成。

    如果您想启动比您的函数的作用域更长的协程,您需要使用一些CoroutineScope 来启动该协程。

    获取作用域的常用方法是使您的函数成为CoroutineScope 的扩展。然后,调用者负责为您的函数提供协程范围(就像设计了 launch 和 async 一样)。此外,通常这些函数是非挂起的(因为挂起函数预计会在返回之前完成所有工作)。

    fun CoroutineScope.coroutiny(): String {
        launch(Dispatchers.IO) {
            delay(1000)
            println("Independent thing that should not avoid coroutiny to return")
        }
        return "Coroutiny return"
    }
    
    
    fun main() = runBlocking {
        println(coroutiny())
        println("after coroutiny")
        delay(2000)
    }
    

    输出(try it yourself):

    Coroutiny return
    after coroutiny
    Independent thing that should not avoid coroutiny to return
    

    如果您的函数存在于具有生命周期的组件中,并且如果将启动的协程的生命周期与该生命周期联系起来是有意义的,那么另一种选择是创建一个 CoroutineScope 作为该组件的属性,并且在组件生命周期结束时适当地取消它。

    class MyClassWithLifecycle {
    
        private val scope = CoroutineScope(CoroutineName("my-custom-processing"))
    
        fun coroutiny(): String {
            scope.launch(Dispatchers.IO) {
                delay(1000)
                println("Independent thing that should not avoid coroutiny to return")
            }
            return "Coroutiny return"
        }
    
    
        fun someCloseOrDisposeFunction() {
            scope.cancel()
        }
    }
    

    如果您使用的是 Android,Kotlin 扩展库会提供开箱即用的这些范围,例如最重要的具有生命周期的组件中的 lifecycleScope 或 viewModelScope。

    【讨论】:

    • 如果我没有调用 delay(2000) 会发生什么? customScope 中的内容是否仍然运行完成?
    • 是的,runBlocking 确保所有子协程在返回之前完成。我离开 delay() 只是因为它在您的初始代码中,但它不是必需的。
    【解决方案2】:

    您将需要另一个比原来的 coroutineScope 寿命更长的示波器。

    val myCustomScope = CoroutineScope(Job())
    
    suspend fun coroutiny(): String {
        myCustomScope.launch(Dispatchers.IO) {
            delay(1000)
            println("Independent thing that should not avoid coroutiny to return")
     }
     return "Coroutiny return"
    }
    
    suspend fun main() {
        println(coroutiny())
        println("after coroutiny")
        delay(2000)
        myCustomScope.cancel() // Cancel the scope at the right time
    }
    

    测试它here。

    请确保在不再需要 myCustomScope 时取消它。

    【讨论】:

      【解决方案3】:

      你要的是GlobalScope.launch。

      不要发明另一种方法来做完全相同的事情。

      如果使用GlobalScope 来实现您想要的东西有问题,那么问题在于想要这样做,而不是使用GlobalScope 来实现它。

      特别是,在建议不要使用 GlobalScope 时,Kotlin 团队建议您应该管理您正在启动的所有这些协程的生命周期。至少您应该确保未完成的协程不会无限制地累积。如果调用者的 coroutineScope 没有正确封装协程的生命周期,那么您应该有一个与 不同的范围,并将其作为参数传入。

      【讨论】:

      • 我不太明白你的意思。使用GlobalScope 确实与从函数中启动异步操作不同。即使有 OP 的要求,也绝对可以使用正确的范围。
      • @Joffrey 我认为您提供给 OP 的第一个解决方案不会按照他的要求启动通常被视为“后台进程”的内容。它启动的协程的生命周期将受到父协程的限制。您的第二个部分解决方案符合我在这里提倡的 - 要求 OP 改变他的要求。
      • 我认为当OP写“后台进程”时,他的意思是“异步计算”。我不相信有任何实际产生比程序持续时间更长的进程的意图,OP 只是想要一些比函数调用更长寿的东西。所以是的,我相信 OP 的意图只是开始一个异步计算,而且范围仍然有限(但只是更宽)的事实在这里实际上是受欢迎的(OP 正在寻找比GlobalScope 更窄的范围),所以这不是 AFAICT 要求的变化。
      【解决方案4】:

      在我得到的答案的帮助下,这是实现我真正想要的最佳方式:

      package com.bmw.otd.validation
      
      import kotlinx.coroutines.*
      
      fun CoroutineScope.coroutiny(): String {
          scope.launch(Dispatchers.IO) {
              delay(1000)
              println("Independent thing that should not avoid coroutiny to return")
          }
      
          return "Coroutiny return"
      }
      
      
      suspend fun main() = coroutineScope {
          println(coroutiny(this))
          println("after coroutiny")
      }
      
      

      通过这种方式,我保证:

      • 函数不等待启动的协程完成 返回。
      • 在后台工作完成之前,应用程序不会完成,而我的测试中的新“嵌套范围”无法保证这一点。

      【讨论】:

      • 请注意,将其作为 CoroutineScope 的扩展功能,因为建议的公认答案更接近惯例。这是 coroutines 库中使用的模式,在某些情况下更容易使用,因为使用非扩展函数有时您要使用的 this 来自外部范围,因此您必须指定哪个具有适当的@ 标签。
      • 我会编辑我的答案,谢谢。
      猜你喜欢
      • 2021-12-30
      • 1970-01-01
      • 2020-11-05
      • 2011-06-16
      • 1970-01-01
      • 2017-12-23
      • 2019-09-02
      • 2021-10-24
      • 2021-01-07
      相关资源
      最近更新 更多