我从迁移tornado
到asyncio
了,我找不到asyncio
等效tornado
的PeriodicCallback
。(APeriodicCallback
有两个参数:要运行的函数和两次调用之间的毫秒数。)
- 有这样的等同物
asyncio
吗? - 如果没有,那么在不冒
RecursionError
一会儿风险的情况下,最干净的方法是什么呢?
我从迁移tornado
到asyncio
了,我找不到asyncio
等效tornado
的PeriodicCallback
。(APeriodicCallback
有两个参数:要运行的函数和两次调用之间的毫秒数。)
asyncio
吗?RecursionError
一会儿风险的情况下,最干净的方法是什么呢?await asyncio.sleep(time)
到您的功能。
LoopingCall
实现。
Answers:
对于3.5以下的Python版本:
import asyncio
@asyncio.coroutine
def periodic():
while True:
print('periodic')
yield from asyncio.sleep(1)
def stop():
task.cancel()
loop = asyncio.get_event_loop()
loop.call_later(5, stop)
task = loop.create_task(periodic())
try:
loop.run_until_complete(task)
except asyncio.CancelledError:
pass
对于Python 3.5及更高版本:
import asyncio
async def periodic():
while True:
print('periodic')
await asyncio.sleep(1)
def stop():
task.cancel()
loop = asyncio.get_event_loop()
loop.call_later(5, stop)
task = loop.create_task(periodic())
try:
loop.run_until_complete(task)
except asyncio.CancelledError:
pass
PeriodicCallback
对于使用协程的应用程序,我也建议使用这样的循环,而不是一个循环。
stop
函数。即:loop.call_later(5, lambda: task.cancel())
loop.call_later(5, task.cancel)
。
当您认为应该在asyncio程序的“后台”发生某些事情时,这asyncio.Task
也许是个好方法。您可以阅读这篇文章,以了解如何使用任务。
这是定期执行某些功能的类的可能实现:
import asyncio
from contextlib import suppress
class Periodic:
def __init__(self, func, time):
self.func = func
self.time = time
self.is_started = False
self._task = None
async def start(self):
if not self.is_started:
self.is_started = True
# Start task to call func periodically:
self._task = asyncio.ensure_future(self._run())
async def stop(self):
if self.is_started:
self.is_started = False
# Stop task and await it stopped:
self._task.cancel()
with suppress(asyncio.CancelledError):
await self._task
async def _run(self):
while True:
await asyncio.sleep(self.time)
self.func()
让我们测试一下:
async def main():
p = Periodic(lambda: print('test'), 1)
try:
print('Start')
await p.start()
await asyncio.sleep(3.1)
print('Stop')
await p.stop()
await asyncio.sleep(3.1)
print('Start')
await p.start()
await asyncio.sleep(3.1)
finally:
await p.stop() # we should stop task finally
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
输出:
Start
test
test
test
Stop
Start
test
test
test
[Finished in 9.5s]
如您所见,start
我们只是启动任务,该任务调用一些函数并在无尽循环中休眠一些时间。关于,stop
我们只是取消该任务。注意,该程序应在程序完成时停止。
更重要的一件事是您的回调不应该花费太多时间来执行(否则它将冻结事件循环)。如果您打算调用一些长时间运行的程序func
,则可能需要在executor中运行它。
func
其作为协程是一个好主意,因此我们可以:await self.func()
在该_run
方法中?
start()
并不需要async
。
没有对定期呼叫的内置支持。
只需创建您自己的调度程序循环即可休眠并执行任何调度的任务:
import math, time
async def scheduler():
while True:
# sleep until the next whole second
now = time.time()
await asyncio.sleep(math.ceil(now) - now)
# execute any scheduled tasks
await for task in scheduled_tasks(time.time()):
await task()
该scheduled_tasks()
迭代器应该产生对于准备在给定的时间运行的任务。请注意,从理论上讲,制定时间表并启动所有任务可能要花费超过1秒的时间;这里的想法是调度程序产生自上次检查以来应该已经启动的所有任务。
asyncio
事件循环有time()
可能替代的使用方法time
模块。
loop.time
方法的状态“这是因为一个划时代以秒表示浮子,但该历元,精度,准确度和漂移是不确定的,并且可能根据事件循环不同。” 在这里,我将其解释为“自纪元以来的SI秒”,因此CPU时间滴答作响,或者其他非“统一”时钟不符合loop.time()
。由于OP只是要求每x毫秒进行一次定期回调,因此在我看来这loop.time()
已经足够了。
一种变体可能会有所帮助:如果您希望您的重复调用每n秒而不是上一次执行的结束与下一次执行的开始之间的n秒发生一次,并且您不希望调用在时间上重叠,请执行以下操作:更简单:
async def repeat(interval, func, *args, **kwargs):
"""Run func every interval seconds.
If func has not finished before *interval*, will run again
immediately when the previous iteration finished.
*args and **kwargs are passed as the arguments to func.
"""
while True:
await asyncio.gather(
func(*args, **kwargs),
asyncio.sleep(interval),
)
以及使用它在后台运行几个任务的示例:
async def f():
await asyncio.sleep(1)
print('Hello')
async def g():
await asyncio.sleep(0.5)
print('Goodbye')
async def main():
t1 = asyncio.ensure_future(repeat(3, f))
t2 = asyncio.ensure_future(repeat(2, g))
await t1
await t2
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
await repeat(3, f)
和await repeat(2, g)
?
带有python 3.7装饰器的替代版本
import asyncio
import time
def periodic(period):
def scheduler(fcn):
async def wrapper(*args, **kwargs):
while True:
asyncio.create_task(fcn(*args, **kwargs))
await asyncio.sleep(period)
return wrapper
return scheduler
@periodic(2)
async def do_something(*args, **kwargs):
await asyncio.sleep(5) # Do some heavy calculation
print(time.time())
if __name__ == '__main__':
asyncio.run(do_something('Maluzinha do papai!', secret=42))
基于@A。Jesse Jiryu Davis的回复(带有@TorkelBjørnson-Langen和@ReWrite注释),这是一种避免漂移的改进。
import time
import asyncio
@asyncio.coroutine
def periodic(period):
def g_tick():
t = time.time()
count = 0
while True:
count += 1
yield max(t + count * period - time.time(), 0)
g = g_tick()
while True:
print('periodic', time.time())
yield from asyncio.sleep(next(g))
loop = asyncio.get_event_loop()
task = loop.create_task(periodic(1))
loop.call_later(5, task.cancel)
try:
loop.run_until_complete(task)
except asyncio.CancelledError:
pass
periodic
应该loop.time()
优先使用,time.time()
因为loop.time()
是内部使用了时间参考asyncio.sleep()
。loop.time()
返回单调时间,而time.time()
返回挂钟时间。两者将有所不同,例如,当系统管理员修改系统上的日期或NTP调整挂钟时间时。