gpt4 book ai didi

kotlin - 在 Kotlin 中创建子协程范围

转载 作者:行者123 更新时间:2023-12-04 12:02:02 27 4
gpt4 key购买 nike

短(-ish)故事
我想知道是否有或多或少的标准方法来创建协程上下文/范围,以便:

  • 它是用于结构化并发的当前协程的子进程,
  • 它可以存储在某些属性等中,然后用于运行异步任务,例如launch() .

  • coroutineScope()正是我需要的,它创建了一个子作用域,但它并不简单地将它返回给调用者——我们需要传递一个 lambda 并且协程生命周期仅限于这个 lambda 的执行。另一方面, CoroutineScope() factory 创建了一个长期运行的作用域,我可以存储以备后用,但它与当前的协程无关。
    我能够手动创建这样一个范围:
    suspend fun createChildCoroutineScope(): CoroutineScope {
    val ctx = coroutineContext
    return CoroutineScope(ctx + Job(ctx.job))
    }
    乍一看,它似乎完全符合我的需要。是否等同于 coroutineScope()或者我的解决方案在某种程度上不完整,我应该执行一些额外的任务?我试图阅读 coroutineScope()的源代码,但相当复杂。是否有更简单或更标准的方法来创建子作用域?
    另外,它是否被认为是一种不好的做法或反模式?我只是担心如果已经没有这样一个简单的函数,那么可能是有原因的,我不应该以这种方式真正使用协程。
    用例(更长的故事)
    通常,当我实现某种可以异步安排后台操作的长时间运行的服务时,我会看到这种需求:
    class MyService {
    fun scheduleSomeTask() {
    // start task in the background
    // return immediately
    }
    }
    使用协程有几种可能性:
  • GlobalScope ,但很糟糕。
  • 制作 scheduleSomeTask()使用当前协程暂停和运行后台任务。在许多情况下,我认为这种方法并不是真正合适的方法:
  • 后台任务由调用者“拥有”,而不是由服务本身“拥有”。如果我们例如停止服务,后台任务仍将运行。
  • 它要求调度功能是可挂起的。我认为这是错误的,因为我真的不明白为什么不允许某些 Java 代码或协程上下文之外的代码在我的服务中安排任务的原因。

  • 给我的服务一个定义的生命周期,用 CoroutineScope() 创建范围和 cancel()它在停止/破坏时。这很好,但我认为我们仍然可以从协程的结构化并发中受益,所以对我来说,我的服务分离是一个缺点。
    例如,我们有一个文件下载服务,它由(拥有)其他服务组成,包括数据缓存服务。用典型的方法 start()/stop()我们需要手动控制生命周期的服务,并且很难正确处理故障。协程让它变得更容易:如果缓存服务崩溃,它会自动传播到下载服务;如果下载服务需要停止,它只是取消它的协程,它可以确保它不会泄漏它的任何子组件。所以对我来说,在设计由几个小服务组成的应用程序时,协程的结构化并发可能非常有用。

  • 我目前的方法是这样的:
    class MyService {
    private lateinit var coroutine : CoroutineScope

    suspend fun start() {
    coroutine = createChildCoroutineScope() + CoroutineName("MyService")
    }

    fun stop() {
    coroutine.cancel()
    }

    fun scheduleSomeTask() {
    coroutine.launch {
    // do something
    }
    }
    }
    或者,或者:
    class MyService(
    private val coroutine: CoroutineScope
    ) {
    companion object {
    suspend fun start() = MyService(createChildCoroutineScope())
    }
    }
    通过这种方式,服务“拦截”启动它的协程并将其后台操作附加到它。但正如我所说,我不确定这是否出于某种原因不被视为反模式。
    另外,我了解我的 createChildCoroutineScope()有潜在危险。通过调用它,我们使当前的协程无法完成。这可能是库中不存在此类函数的原因。另一方面,它与执行以下操作并没有真正不同:
    launch {
    while (true) {
    socket.accept() // assume it is suspendable, not blocking
    // launch connection handler
    }
    }
    事实上,从技术角度来看,这两种方法非常相似。它们具有相似的并发结构,但我相信“我的”方法通常更简洁、更强大。

    最佳答案

    我找到了对我的问题的非常好的答案和解释。 Roman Elizarov 在他的一篇文章中准确地讨论了我的问题:https://elizarov.medium.com/coroutine-context-and-scope-c8b255d59055
    他解释说,虽然在技术上可以“捕获”挂起函数的当前上下文并使用它来启动后台协程,但强烈建议不要这样做:

    Do not do this! It makes the scope in which the coroutine is launched opaque and implicit, capturing some outer Job to launch a new coroutine without explicitly announcing it in the function signature. A coroutine is a piece of work that is concurrent with the rest of your code and its launch has to be explicit.

    If you need to launch a coroutine that keeps running after your function returns, then make your function an extension of CoroutineScope or pass scope: CoroutineScope as parameter to make your intent clear in your function signature. Do not make these functions suspending.


    我知道我可以通过 CoroutineScope/ CoroutineContext到一个函数,但我认为挂起函数是一种更短、更优雅的方法。然而,上述解释使 hell 很有意义。如果我们的函数需要获取调用者的协程范围/上下文,请明确说明这一点 - 再简单不过了。
    这也与“热”/“冷”执行的概念有关。挂起函数的一大好处是它们允许我们轻松创建长期运行任务的“冷”实现。虽然我认为在协程文档中没有明确指定挂起函数应该是“冷的”,但满足这个要求通常是一个好主意,因为我们的挂起函数的调用者可能会认为它是“冷的”。捕获协程上下文使我们的函数“热”,因此应该通知调用者这一点。

    关于kotlin - 在 Kotlin 中创建子协程范围,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67749075/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com