Delay()准确性问题/作业调度程序的怪异行为

时间:2019-01-17 02:52:48

标签: kotlin scheduler coroutine kotlinx.coroutines

我目前正在尝试构建一个工作计划程序,如下所示。我的目标是能够安排任意功能(在这里,(长)->单位)的启动时间,并尽可能精确地安排其启动时间(亚毫秒为理想选择)。

>lua -e "io.stdout:setvbuf 'no'" "RecordLockEvent.lua" 
Request Body: {"lockId": "3","eventType": "LOCKED","eventDateTime": "2019-01-15 19:54:37","eventUser": "KG - Test"}
Request Headers:
Content-Type    application/json
Content-Length  102
command status: 1
http status: 200
response status: HTTP/1.1 200 200
response headers: 
  connection    Upgrade, close
  content-type  application/json
  upgrade   h2
  date  Wed, 16 Jan 2019 00:54:37 GMT
  content-length    22
  server    Apache/2.4.33 (CentOS)
response body: 
  1 {"response":"FAILURE"}
>Exit code: 0

但是,即使使用上面的非常简单的示例(在main()中),我也获得了计划时间与实际运行计划功能的时间之间的明显时间差。有些甚至在预定时间之前运行(请参见下面的最后一行,负时差),这对我来说仍然是个谜。

如何在调用delay()之前运行回调?

谢谢!

import java.util.*
import kotlinx.coroutines.*
import java.util.concurrent.PriorityBlockingQueue
import kotlin.math.max
import java.time.Instant

fun nowInMicrosSinceEpoch() : Long {
    val now = Instant.now()
    return now.toEpochMilli() * 1000L + (now.getNano().toLong() / 1000L)
}

open class TimeCallback(open var time : Long, open val callback : (Long) -> Unit) {
    open fun run(){
        callback(time)
    }

    override fun toString() : String {
        return "(TimeCallback - T:${time/1000L})"
    }
}

class PulseCallback(override var time : Long,
                    override val callback : (Long) -> Unit,
                    val pulsePeriod : Long,
                    val callbackQueue : AbstractQueue<TimeCallback>) : TimeCallback(time, callback) {
    override fun run(){
        callback(time)
        time += pulsePeriod
        callbackQueue.add(this)
    }

    override fun toString() : String {
        return "(PulseCallback - T:${time/1000L} - PP:${pulsePeriod/1000L})"
    }
}

abstract class Clock {
    protected abstract var currentTime: Long
    protected val comparator : Comparator<TimeCallback> = compareBy<TimeCallback> { x -> x.time }

    abstract fun start()
    abstract fun stop()
    abstract fun addCallback(time: Long, callback: (Long) -> Unit)
    abstract fun addPulseCallback(time: Long, pulsePeriod: Long, callback: (Long) -> Unit)
    abstract fun getTime() : Long
}

class LiveClock : Clock() {
    override var currentTime : Long = nowInMicrosSinceEpoch()
    private val callbacks : PriorityBlockingQueue<TimeCallback> = PriorityBlockingQueue<TimeCallback>(10000, comparator)

    private var clockCoroutine : Job? = null

    override fun start(){
        clockCoroutine = GlobalScope.launch {
            try{
                var waitTime : Long
                while(true) {
                    println(callbacks)
                    val callback: TimeCallback = callbacks.take()
                    currentTime = nowInMicrosSinceEpoch()
                    waitTime = max(callback.time - currentTime, 0L) / 1000L
                    println("Now is ${currentTime/1000L}, waiting $waitTime ms until ${callback.time/1000L}")
                    delay(waitTime)
                    callback.run()
                }
            } finally {
                println("Clock was stopped by CancellationException.")
            }
        }
    }

    override fun stop(){
        // Cannot stop before starting!
        clockCoroutine!!.cancel()
    }

    override fun addCallback(time: Long, callback: (Long) -> Unit){
        callbacks.add(TimeCallback(
            time = time,
            callback = callback
        ))
    }

    override fun addPulseCallback(firstPulse: Long, pulsePeriod: Long, callback: (Long) -> Unit){
        callbacks.add(PulseCallback(
            time = firstPulse,
            pulsePeriod = pulsePeriod,
            callback = callback,
            callbackQueue = callbacks
        ))
    }

    override fun getTime() : Long {
        return nowInMicrosSinceEpoch()
    }
}

fun printTest(t : Long){
    println("Time difference: ${nowInMicrosSinceEpoch()/1000L - (t/1000L)} ms")
}

fun main(args: Array<String>) {
    val clock = LiveClock()
    clock.addPulseCallback(nowInMicrosSinceEpoch(), 1000*1000L, ::printTest)
    clock.addPulseCallback(nowInMicrosSinceEpoch(), 500*1000L, ::printTest)
    clock.start()
    runBlocking {
        // Run for 100 seconds...
        delay(100000L)
    }
}

1 个答案:

答案 0 :(得分:1)

nowInMicrosSinceEpoch()的实现是错误的。毫秒值被应用了两次。

为了说明这一点,这是Java代码,用于打印nowInMicrosSinceEpoch()中使用的值:

Instant now = Instant.now();
System.out.println(now);
System.out.printf("%23d        toEpochMilli()%n", now.toEpochMilli());
System.out.printf("%26d     toEpochMilli() * 1000 = a%n", now.toEpochMilli() * 1000L);
System.out.printf("%29d  getNano()%n", now.getNano());
System.out.printf("%26d     getNano() / 1000 = b%n", now.getNano() / 1000L);
System.out.printf("%26d     a + b%n", now.toEpochMilli() * 1000L + now.getNano() / 1000L);

输出

2019-02-02T00:16:58.999999999Z
          1549066618999        toEpochMilli()
          1549066618999000     toEpochMilli() * 1000 = a
                    999999999  getNano()
                    999999     getNano() / 1000 = b
          1549066619998999     a + b

因此,当时钟从x:58.999999999Z转到x:59.000000000Z时,您会得到:

2019-02-02T00:16:59.000000000Z
          1549066619000        toEpochMilli()
          1549066619000000     toEpochMilli() * 1000 = a
                    000000000  getNano()
                    000000     getNano() / 1000 = b
          1549066619000000     a + b

晚1纳秒的值将返回早于998999微秒的值。
计算值以两倍的速度运行,并且每秒跳回1秒。

正确的公式是(在Java中):

Instant now = Instant.now();
return now.getEpochSecond() * 1000000L + now.getNano() / 1000L;