具有请求队列的Kotlin服务

具有请求队列的Kotlin服务,kotlin,architecture,future,deferred,kotlin-coroutines,Kotlin,Architecture,Future,Deferred,Kotlin Coroutines,我想使用以下API设计一个服务: suspend fun getUsers(请求:请求):列表 在引擎盖下,我会向服务器发送请求(不管如何,但可以说这是一个反应式WebClient),但这里有一个技巧:我只能每隔500毫秒发送一次请求,否则我会出错 有人能推荐我如何实现它吗?当我从一个挂起的协程调用getUsers时,工作单元被添加到具有此方法的服务的某个队列中,然后在某个时间点实现并返回结果 我假设我可以使用一些ReceiveChannel作为一个队列,为它的元素设置一个for循环,里面有一个

我想使用以下API设计一个服务:

suspend fun getUsers(请求:请求):列表

在引擎盖下,我会向服务器发送请求(不管如何,但可以说这是一个反应式
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()
        }
    }
}
我建议:

  • 将泛型向下推到函数级别
  • 使用参与者而不是协同程序实现(但您可能更喜欢这样)
无论哪种方式,此解决方案都应该允许您使用队列的单个实例来处理所有请求的延迟,而不管返回类型如何。(抱歉,我重新命名了一些东西以帮助我自己的概念化,希望这仍然有意义):

private const val REQUEST_INTERVAL=500
接口DelayedRequestHandler{
暂停具有延迟的有趣手柄(块:()->T):T
}
类DelayedRequestHandlerImpl(requestInterval:Int=REQUEST\u INTERVAL):DelayedRequestHandler,CoroutineScope{
private val job=job()
override val coroutineContext=调度程序。未定义+作业
private val delayedHandlerator=delayedRequestHandlerator(请求间隔)
用延迟覆盖暂停操作手柄(块:()->T):T{
val result=CompletableDeferred()
delayedHandlerator.send(DelayedHandlerMsg(结果,块))
返回结果。等待()
}
}
私有数据类DelayedHandlerMsg(val结果:CompletableDeferred,val块:()->result)
private fun CoroutineScope.delayedRequestHandlerator(requestInterval:Int)=actor(){
var lastRequestTime:LocalDateTime=LocalDateTime.now()
用于(通道中的消息){
试一试{
println(“获得消息处理”)
val now=LocalDateTime.now()
val diff=时间间隔(lastRequestTime,现在)
if(差值<请求间隔){
延迟(请求间隔-差异)
}
lastRequestTime=LocalDateTime.now()
@抑制(“未选中的_CAST”)
val msgCast=作为DelayedHandlerMsg的消息
val结果=msgCast.block()
println(结果)
msgCast.result.complete(结果)
}捕获(e:例外){
message.result.complete异常(e)
}
}
}
fun main()=运行阻塞{
val mydelayHandler=DelayedRequestHandlerImpl(2000)
val作业=列表(10){
发射{
mydelayHandler.handleWithDelay{
“结果$it”
}
}
}
jobs.forEach{it.join()}
}

所以这是我提出的最后一个实现。请注意
SupevisorJob
,因为我们不希望在其中一个请求失败时停止处理,这是完全可能的,也很好(至少在我的情况下)

另外,@Laurence建议的选项可能更好,但我决定暂时不使用actors,因为API被标记为过时

@Service
class DelayingRequestSenderImpl(@Value("\${vk.request.interval}") private val interval: Int) : DelayingRequestSender {
    private var lastRequestTime: LocalDateTime = LocalDateTime.now()
    private val requestChannel: Channel<Deferred<*>> = Channel()
    //SupervisorJob is used because we want to have continuous processing of requestChannel
    //even if one of the requests fails
    private val coroutineScope = CoroutineScope(SupervisorJob())

    override suspend fun <T> request(block: () -> T): T {
        val deferred = coroutineScope.async(start = CoroutineStart.LAZY) { block() }
        requestChannel.send(deferred)
        return deferred.await()
    }

    @PostConstruct
    fun startProcessing() = coroutineScope.launch {
        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()
        }
    }
}
@服务
类DelayingRequestSenderImpl(@Value(\${vk.request.interval})私有val interval:Int):DelayingRequestSender{
私有变量lastRequestTime:LocalDateTime=LocalDateTime.now()
私有val请求通道:通道=通道()
//之所以使用SupervisorJob,是因为我们希望对requestChannel进行连续处理
//即使其中一个请求失败
private val coroutineScope=coroutineScope(SupervisorJob())
覆盖挂起乐趣请求(块:()->T):T{
val deferred=coroutineScope.async(start=CoroutineStart.LAZY){block()}
requestChannel.send(延迟)
返回延迟。等待()
}
@施工后
fun startProcessing()=coroutineScope.launch{
for(请求通道中的请求){
val now=LocalDateTime.now()
val diff=时间间隔(lastRequestTime,现在)
if(差值<间隔){
延迟(间隔-差异)
}
lastRequestTime=LocalDateTime.now()
request.start()
}
}
}

无法帮助您完成合作计划,但我想到了一些想法。是否允许每隔500毫秒从客户端调用服务器?或者从您的请求发件人的每个实例?让一个不使用泛型而使用原始字符串的单例请求发送者(如果第一种情况是这样的话)怎么样。。而不是使用另一种更高级别的服务,将数据解析为对象,这些对象可能是多个实例……感谢您的建议!但是,
actor
标记为
@ObsoleteCoroutinesApi
,因此我认为最好尽量避免使用it@LeonidBor是的,但是这里解释了它的语义,现在没有其他选择,但是它们将在将来出现,并且可以进行迁移。它们仍然是通过通道通信封装线程安全状态的正确方法。你能帮我吗
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() }
}
@Service
class DelayingRequestSenderImpl(@Value("\${vk.request.interval}") private val interval: Int) : DelayingRequestSender {
    private var lastRequestTime: LocalDateTime = LocalDateTime.now()
    private val requestChannel: Channel<Deferred<*>> = Channel()
    //SupervisorJob is used because we want to have continuous processing of requestChannel
    //even if one of the requests fails
    private val coroutineScope = CoroutineScope(SupervisorJob())

    override suspend fun <T> request(block: () -> T): T {
        val deferred = coroutineScope.async(start = CoroutineStart.LAZY) { block() }
        requestChannel.send(deferred)
        return deferred.await()
    }

    @PostConstruct
    fun startProcessing() = coroutineScope.launch {
        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()
        }
    }
}