一次启动一个协程 - Kotlin

Launch one Coroutine at a time - Kotlin

我们在 UI 中有一个按钮,当按下时,它会在它自己的 coroutine 中进行一些远程网络调用。但是,如果用户出于某种原因向按钮发送垃圾邮件,则远程数据可能会以某种方式损坏。我们希望通过在当前请求完成之前丢弃所有请求来防止这种情况。

有很多方法可以做到这一点。如果 CoroutineScope 未激活,我已经在 CoroutineScope 上创建了一个简单的扩展函数,仅用于 launch。这是我创建的:

扩展函数

fun CoroutineScope.safeLaunch(dispatcher: CoroutineDispatcher, block: () -> Unit): Job {
    return if (!isActive) {
        launch(dispatcher) {
            block()
        }
    } else {
        launch {}
    }
}

示例使用

fun loadNotifications() {
    viewModelScope.safeLaunch(IO) {
        getNotifications.invoke() // Suspend function invoke should only be from a coroutine or another suspend function
    }
}

问题是,上面的代码无法编译,因为我收到一条错误消息

Suspend function invoke should only be from a coroutine or another suspend function

有谁知道我做错了什么或如何让它发挥作用?

此代码存在多个问题:

  1. 修复您提到的错误非常简单,只需将 block 指定为可暂停:block: suspend () -> Unit.
  2. isActive 并不意味着 job/scope 正在积极地 运行 某事,而是它还没有完成。 isActive 在您的示例中总是 returns true,甚至在启动任何协程之前也是如此。
  3. 如果您的服务器无法处理并发操作,那么您真的应该在服务器端解决这个问题。限制客户端不是一个合适的修复方法,因为它仍然可以被用户利用。另外,您需要记住,多个客户端可以同时执行相同的操作。

正如您所提到的,有几种方法可以在客户端处理这种情况:

  1. 在 UI 和按钮的情况下,禁用按钮或用加载指示器覆盖 screen/button 可能对用户体验来说是最好的。给用户反馈后台操作运行,同时修复多次调用服务器的问题

  2. 一般情况下,如果我们只需要限制并发并拒绝任何额外的任务,而最后一个仍然是运行,可能最简单的是使用Mutex

private val scope = CoroutineScope(EmptyCoroutineContext)
private val mutex = Mutex()

fun safeLaunch(block: suspend () -> Unit) {
    if (!mutex.tryLock()) {
        return
    }

    scope.launch {
        try {
            block()
        } finally {
            mutex.unlock()
        }
    }
}

请注意,我们需要每个作用域或每个任务类型有一个单独的互斥体。 我认为不可能创建通用扩展函数这样的实用程序,在任何协程范围内工作。实际上,我们可以用与您的原始代码非常相似的方式实现它,但是通过查看当前作业的 children。不过,我认为这样的解决方案是黑客行为,我不鼓励这样做。