一次启动一个协程 - 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
有谁知道我做错了什么或如何让它发挥作用?
此代码存在多个问题:
- 修复您提到的错误非常简单,只需将
block
指定为可暂停:block: suspend () -> Unit
.
isActive
并不意味着 job/scope 正在积极地 运行 某事,而是它还没有完成。 isActive
在您的示例中总是 returns true
,甚至在启动任何协程之前也是如此。
- 如果您的服务器无法处理并发操作,那么您真的应该在服务器端解决这个问题。限制客户端不是一个合适的修复方法,因为它仍然可以被用户利用。另外,您需要记住,多个客户端可以同时执行相同的操作。
正如您所提到的,有几种方法可以在客户端处理这种情况:
在 UI 和按钮的情况下,禁用按钮或用加载指示器覆盖 screen/button 可能对用户体验来说是最好的。给用户反馈后台操作运行,同时修复多次调用服务器的问题
一般情况下,如果我们只需要限制并发并拒绝任何额外的任务,而最后一个仍然是运行,可能最简单的是使用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。不过,我认为这样的解决方案是黑客行为,我不鼓励这样做。
我们在 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
有谁知道我做错了什么或如何让它发挥作用?
此代码存在多个问题:
- 修复您提到的错误非常简单,只需将
block
指定为可暂停:block: suspend () -> Unit
. isActive
并不意味着 job/scope 正在积极地 运行 某事,而是它还没有完成。isActive
在您的示例中总是 returnstrue
,甚至在启动任何协程之前也是如此。- 如果您的服务器无法处理并发操作,那么您真的应该在服务器端解决这个问题。限制客户端不是一个合适的修复方法,因为它仍然可以被用户利用。另外,您需要记住,多个客户端可以同时执行相同的操作。
正如您所提到的,有几种方法可以在客户端处理这种情况:
在 UI 和按钮的情况下,禁用按钮或用加载指示器覆盖 screen/button 可能对用户体验来说是最好的。给用户反馈后台操作运行,同时修复多次调用服务器的问题
一般情况下,如果我们只需要限制并发并拒绝任何额外的任务,而最后一个仍然是运行,可能最简单的是使用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。不过,我认为这样的解决方案是黑客行为,我不鼓励这样做。