asyncio.loop.time()可与datetime.datetime.now()进行比较吗?

时间:2019-04-09 11:42:24

标签: python python-3.x python-asyncio

我希望使用asyncio.loop在特定时间设置回调。我的问题是我需要根据datetime.datetime对象(UTC)安排这些时间,但是asyncio.loop.call_at()使用内部参考时间。

对在Ubuntu上运行的python 3.7.3进行的快速测试表明,asyncio.loop.time()正在报告系统正常运行时间。为了进行转换,我首先想到的是天真的存储参考时间并在以后使用:

from asyncio import new_event_loop
from datetime import datetime, timedelta

_loop = new_event_loop()
_loop_base_time = datetime.utcnow() - timedelta(seconds=_loop.time())

def schedule_at(when, callback, *args):
    _loop.call_at((when - _loop_base_time).total_seconds(), callback, *args)

但是,尚不清楚此偏移量(datetime.utcnow() - timedelta(seconds=loop.time()))是否稳定。我不知道即使在修改系统时钟的情况下(例如:通过NTP更新),系统正常运行时间与UTC相比是否也会漂移。

请记住,这是针对可能一次运行数月的监视软件而言,小偏差可能非常重要。我应该注意,我发现没有NTP守护程序的系统每天会损失数分钟,而一次NTP更新会在很短的时间内将时间偏移几分钟。由于我不知道两者是否保持同步,因此尚不清楚我需要关注多少。


注意:我知道python在未来24小时以上安排事件的问题。通过将较远的将来事件存储在列表中并每12小时轮询一次即将发生的事件,并仅在将来的时间少于24小时时进行调度,我将解决这个问题。


是否可以可靠地从datetime.datetime转换为asyncio.loop次?还是两个时间系统无法比拟?如果它们具有可比性,那么我需要采取什么特殊措施来确保计算正确无误。

2 个答案:

答案 0 :(得分:1)

您可以使用与用于调度的时间框架相同的时间框架来计算以秒为单位的差异,然后将asyncio.call_later用于计算出的延迟:

def schedule_at(when, callback, *args):
  delay = (when - datetime.utcnow()).total_seconds()
  _loop.call_later(delay, callback, *args)

这将解决以下问题:循环时间与utcnow之间的差是否稳定;它仅需要在计划任务的时间和执行时间之间保持稳定(根据您的说明,该时间应少于12小时)。

例如:如果事件循环的内部时钟每小时从utcnow偏移1秒(故意的极端例子),则每个任务最多偏移12秒,但几个月不会累积此错误运行时。与使用固定参考的方法相比,此方法可提供更好的保证。

答案 1 :(得分:0)

另一种方法是根本不依赖循环内部时钟。您可以在后台运行任务,并定期检查是否应执行回调。

此方法的准确性与您在下次检查之前等待的时间相对应,但是考虑到其他任何可能的准确性(例如,Python GC的世界末日),我认为这并不重要。

好的一面是,您不受24小时的限制。

此代码显示了主要思想:

import asyncio
import datetime



class Timer:
    def __init__(self):
        self._callbacks = set()
        self._task = None

    def schedule_at(self, when, callback):        
        self._callbacks.add((when, callback,))
        if self._task is None:
            self._task = asyncio.create_task(self._checker())

    async def _checker(self):
        while True:
            await asyncio.sleep(0.01)
            self._exec_callbacks()

    def _exec_callbacks(self):
        ready_to_exec = self._get_ready_to_exec()
        self._callbacks -= ready_to_exec

        for _, callback in ready_to_exec:
            callback()

    def _get_ready_to_exec(self):
        now = datetime.datetime.utcnow()
        return {
            (when, callback,)
            for (when, callback,)
            in self._callbacks
            if when <= now
        }


timer = Timer()


async def main():
    now = datetime.datetime.utcnow()
    s1_after = now + datetime.timedelta(seconds=1)
    s3_after = now + datetime.timedelta(seconds=3)
    s5_after = now + datetime.timedelta(seconds=5)

    timer = Timer()
    timer.schedule_at(s1_after, lambda: print('Hey!'))
    timer.schedule_at(s3_after, lambda: print('Hey!'))
    timer.schedule_at(s5_after, lambda: print('Hey!'))

    await asyncio.sleep(6)


if __name__ ==  '__main__':
    asyncio.run(main())