Python asyncio.loop.time()与datetime.datetime.now()可比吗?如何比较?
我希望使用Python asyncio.loop.time()与datetime.datetime.now()可比吗?如何比较?,python,python-3.x,python-asyncio,Python,Python 3.x,Python Asyncio,我希望使用asyncio.loop在特定时间设置回调。我的问题是,我需要根据datetime.datetimeobjects(UTC)安排这些时间,但是asyncio.loop.call\u at()使用内部参考时间 在Ubuntu上运行的Python3.7.3上的快速测试表明,asyncio.loop.time()正在报告系统正常运行时间。对于转换,我的第一个想法是天真地存储参考时间并在以后使用: from asyncio import new_event_loop from datetime
asyncio.loop
在特定时间设置回调。我的问题是,我需要根据datetime.datetime
objects(UTC)安排这些时间,但是asyncio.loop.call\u at()
使用内部参考时间
在Ubuntu上运行的Python3.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())
)是否稳定。我不知道与UTC相比,系统启动时间是否会漂移,即使在系统时钟被修改的情况下(例如:通过NTP更新)
记住,这是用于监控软件的,可能一次运行几个月,小的漂移可能非常重要。我应该注意到,如果没有NTP守护进程,系统每天会损失几分钟的时间,一次性的NTP更新可以在短时间内将时间缩短很多分钟。因为我不知道两者是否保持同步,所以不清楚我需要关注多少
注意:我知道python在未来24小时以上安排事件的问题。我将通过将遥远的未来事件存储在一个列表中,并每12小时轮询一次即将到来的事件来解决这个问题,只在未来<24小时时进行调度
是否可以可靠地从
datetime.datetime
转换为asyncio.loop
时间?还是这两个时间系统是不可比拟的?。如果它们是可比较的,我需要做什么来确保我的计算是正确的。您可以使用与用于调度的时间框架相同的时间框架以秒为单位计算差异,然后使用asyncio。稍后使用计算的延迟调用\u
:
def schedule_at(何时,回调,*args):
延迟=(when-datetime.utcnow()).total_seconds()
_稍后调用(延迟、回调、*args)
这将解决循环时间和utcnow
之间的差异是否稳定的问题;它只需要在调度任务的时间和执行任务的时间之间保持稳定(根据您的说明,这应该少于12小时)
例如:如果事件循环的内部时钟每小时从
utcnow
漂移1秒(一个故意的极端示例),则每个任务最多漂移12秒,但不会在运行数月内累积此错误。与使用固定参考的方法相比,这种方法提供了更好的保证 另一种方法是根本不依赖环路内部时钟。您可以在后台运行任务,并定期检查是否应该执行回调
这种方法的不精确性对应于您在下一次检查之前等待的时间,但我认为考虑到任何其他可能的不精确性(例如Python GC的stop the world)并不重要
好的一面是你不受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())
导入异步IO
导入日期时间
类计时器:
定义初始化(自):
self.\u callbacks=set()
self.\u任务=无
def计划_at(自我、何时、回调):
self.\u callbacks.add((何时,回调,))
如果self.\u任务为无:
self.\u task=asyncio.create\u task(self.\u checker())
异步定义检查程序(自身):
尽管如此:
等待异步睡眠(0.01)
self.\u exec\u回调()
def_exec_回调(self):
ready_to_exec=self._get_ready_to_exec()
self.\u回调-=准备好执行
对于u,在ready_中回调到_exec:
回调函数()
def_准备就绪___执行(自我):
now=datetime.datetime.utcnow()
返回{
(何时,回调,)
对于(何时,回调,)
在self.\u回调中
如果正确的秒数不是问题,那么测量经过的秒数就是问题。它需要在调用任务时正确,而不仅仅是在计划任务时正确。漂移问题发生在两个时钟对经过的秒数不一致的情况下。在发生后12小时内重新计算它ce当然可以降低大偏差的风险。这种方法将潜在偏差限制在调度和执行之间可能发生的最大范围内,即12小时,并且不会累积偏差错误(例如,即使您一次运行数月,每个任务在调度时也会重新计算所需的延迟).我认为你不能做得比这更好,除了可能在更接近任务执行时间的地方重新校准(例如,在更接近执行时间的地方安排执行前检查,你将重新计算所需的延迟,并在那里安排任务);但对于大多数实际用途来说,这似乎是矫枉过正。回到这个问题,我意识到系统正常运行时间是基于系统的。这通常与系统时间同步(如timedate.now())但是,如果调整系统时钟,增量容易出现大的跳跃。如果你想在“10分钟内”发生某事,单调时钟是好的;如果你想在晚上10点发生某事,单调时钟是坏的。所以这个答案中提到的1秒漂移是非常误导的。这基本上不是复制了事件循环在内部所做的事情吗?@Avish有点像这样。但是它避免了循环的内部时钟和所有相关问题:24小时限制或可能向日期时间漂移。在版本3.8中更改:在Python 3.7和更早版本中使用默认值事件循环实现,时间和当前时间之间的差异不能超过一天。这在Python 3.8中已经修复。