如何定期使用asyncio执行函数?

时间:2016-05-29 16:17:38

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

我从tornado迁移到asyncio,但我找不到asyncio等同于tornado' s {{ 1}}。 (PeriodicCallback有两个参数:要运行的函数和调用之间的毫秒数。)

  • PeriodicCallback
  • 中有这样的等价物吗?
  • 如果没有,那么实现这一目标的最简洁方法是什么,而不会冒一段时间后获得asyncio的风险?

6 个答案:

答案 0 :(得分:32)

对于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

答案 1 :(得分:17)

当你觉得应该发生的事情"在背景中#34;你的asyncio程序,asyncio.Task可能是一个很好的方法。您可以阅读this post以了解如何使用任务。

这里可能实现定期执行某些功能的类:

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,则可能需要to run it in executor

答案 2 :(得分:12)

没有内置支持定期通话,没有。

只需创建自己的调度程序循环即可休眠并执行任何已安排的任务:

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秒;这里的想法是调度程序产生自上次检查以来应该已经开始的所有任务。

答案 3 :(得分:1)

带有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))

答案 4 :(得分:1)

一个可能有用的变体:如果您希望重复执行的呼叫每n秒而不是在上一次执行的结束与下一次执行的开始之间的n秒之间发生一次,并且您不希望呼叫在时间上重叠,以下更简单:

//article/descendant-or-self::strong[last()]

以及使用它在后台运行一些任务的示例:

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),
        )

答案 5 :(得分:0)

基于@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