我想使用以下API设计服务:
suspend fun getUsers(request: Request): List<User>
在后台,我会将请求发送到服务器(没关系,但是可以说它是响应式WebClient
),但这是一个窍门:我只能每500毫秒发送一次请求,否则会出现错误。
有人可以建议我如何实现该方法,以便当我从协程调用getUsers
时,它挂起时,会将工作单元添加到具有此方法的服务的某些队列中,然后在某个时间点并返回结果?
我假设我可以使用一些ReceiveChannel
作为队列,并在其中使用for
对其元素进行delay
循环,但是我有点迷失了放置此逻辑的位置。这是否应该像将永远运行并被getUsers
调用的后台方法?可能永远不会调用close
方法,因此也可以暂停该方法,但是如何将值从该无限运行方法传递回需要结果的getUsers
呢?
编辑
此刻,我正在考虑这样的解决方案:
private const val REQUEST_INTERVAL = 500
@Service
class DelayedRequestSenderImpl<T> : DelayedRequestSender<T> {
private var lastRequestTime: LocalDateTime = LocalDateTime.now()
private val requestChannel: Channel<Deferred<T>> = Channel()
override suspend fun requestAsync(block: () -> T): Deferred<T> {
val deferred = GlobalScope.async(start = CoroutineStart.LAZY) { block() }
requestChannel.send(deferred)
return deferred
}
@PostConstruct
private fun startRequestProcessing() = GlobalScope.launch {
for (request in requestChannel) {
val now = LocalDateTime.now()
val diff = ChronoUnit.MILLIS.between(lastRequestTime, now)
if (diff < REQUEST_INTERVAL) {
delay(REQUEST_INTERVAL - diff)
lastRequestTime = now
}
request.start()
}
}
}
我在这里看到的问题是,我必须泛化类以使requestChannel
通用,因为请求的结果可能是任何东西。但这意味着DelayedRequestSender
的每个实例都将绑定到一个特定的类型。关于如何避免这种情况的任何建议?
编辑2
这是精制版本。我目前看到的唯一可能的流程是,如果需要或使用反射,我们必须公开@PostConstruct
方法以便编写任何测试。
该想法是不使用GlobalScope
,并且也使用单独的Job
作为处理方法。这是个好方法吗?
interface DelayingSupplier {
suspend fun <T> supply(block: () -> T): T
}
@Service
class DelayingSupplierImpl(@Value("\${vk.request.interval}") private val interval: Int) : DelayingSupplier {
private var lastRequestTime: LocalDateTime = LocalDateTime.now()
private val requestChannel: Channel<Deferred<*>> = Channel()
private val coroutineScope = CoroutineScope(EmptyCoroutineContext)
override suspend fun <T> supply(block: () -> T): T {
val deferred = coroutineScope.async(start = CoroutineStart.LAZY) { block() }
requestChannel.send(deferred)
return deferred.await()
}
@PostConstruct
fun startProcessing() = coroutineScope.launch(context = Job(coroutineScope.coroutineContext[Job])) {
for (request in requestChannel) {
val now = LocalDateTime.now()
val diff = ChronoUnit.MILLIS.between(lastRequestTime, now)
if (diff < interval) {
delay(interval - diff)
}
lastRequestTime = LocalDateTime.now()
request.start()
}
}
}
答案 0 :(得分:1)
我建议:
无论哪种方式,此解决方案都应允许您使用队列的单个实例来处理所有请求的延迟,而与返回类型无关。 (抱歉,我重命名了一些东西来帮助自己进行概念化,希望这仍然有意义):
private const val REQUEST_INTERVAL = 500
interface DelayedRequestHandler {
suspend fun <T> handleWithDelay(block: () -> T): T
}
class DelayedRequestHandlerImpl(requestInterval: Int = REQUEST_INTERVAL) : DelayedRequestHandler, CoroutineScope {
private val job = Job()
override val coroutineContext = Dispatchers.Unconfined + job
private val delayedHandlerActor = delayedRequestHandlerActor(requestInterval)
override suspend fun <T> handleWithDelay(block: () -> T): T {
val result = CompletableDeferred<T>()
delayedHandlerActor.send(DelayedHandlerMsg(result, block))
return result.await()
}
}
private data class DelayedHandlerMsg<RESULT>(val result: CompletableDeferred<RESULT>, val block: () -> RESULT)
private fun CoroutineScope.delayedRequestHandlerActor(requestInterval: Int) = actor<DelayedHandlerMsg<*>>() {
var lastRequestTime: LocalDateTime = LocalDateTime.now()
for (message in channel) {
try {
println("got a message processing")
val now = LocalDateTime.now()
val diff = ChronoUnit.MILLIS.between(lastRequestTime, now)
if (diff < requestInterval) {
delay(requestInterval - diff)
}
lastRequestTime = LocalDateTime.now()
@Suppress("UNCHECKED_CAST")
val msgCast = message as DelayedHandlerMsg<Any?>
val result = msgCast.block()
println(result)
msgCast.result.complete(result)
} catch (e: Exception) {
message.result.completeExceptionally(e)
}
}
}
fun main() = runBlocking {
val mydelayHandler = DelayedRequestHandlerImpl(2000)
val jobs = List(10) {
launch {
mydelayHandler.handleWithDelay {
"Result $it"
}
}
}
jobs.forEach { it.join() }
}
答案 1 :(得分:0)
这是我想出的最后一个实现。请注意<div id="app">
<v-menu offset-y>
<template v-slot:activator="{ on }">
<v-hover>
<v-chip slot-scope="{ hover }"
:class="`elevation-${hover ? 5 : 2}`" v-ripple color="indigo" text-color="white" v-on="on">
<v-avatar>
<v-icon>account_circle</v-icon>
</v-avatar>
Sagar
</v-chip>
</v-hover>
</template>
<v-list>
<v-list-tile
v-for="(item, i) in items"
:key="i"
@click=""
>
<v-list-tile-title>{{ item.title }}</v-list-tile-title>
</v-list-tile>
</v-list>
</v-menu>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
items: [
{ title: 'Click Me' },
{ title: 'Click Me' },
{ title: 'Click Me' },
{ title: 'Click Me 2' }
],
}
}
})
</script>
<style>
.v-chip .v-chip__content{
cursor: pointer;
}
</style>
,因为我们不希望在其中一个请求失败的情况下停止处理,这完全有可能(至少在我看来)。
此外,@ Laurence建议的选项可能会更好,但是由于API被标记为过时,我决定暂时不使用actor。
SupevisorJob