简体   繁体   English

使用 asyncio 在每分钟的开始(00 秒)运行一个函数

[英]Using asyncio to run a function at the start (00 seconds) of every minute

I'm trying to run several functions at the same time (approximately or course) with different parameters and repeat that at the start of every minute.我正在尝试使用不同的参数同时(大约或当然)运行多个函数,并在每分钟开始时重复。

I managed to get an asyncio example to run where I get a function callback to run at specific times with a different parameter, but what I can't figure out is how to run it (and keep running it forever) at very specific times (ie I want to run it at the start of every minute, so at 19:00:00, 19:01:00, etc..).我设法得到了一个asyncio示例来运行,在那里我得到一个函数callback ,以使用不同的参数在特定时间运行,但我无法弄清楚如何在非常特定的时间运行它(并永远运行它)(即我想在每分钟开始时运行它,所以在 19:00:00、19:01:00 等等)。

Asyncio call_at should be able to do that, but it uses a time format that is not the standard python time format and I can't figure out to specify that time format as the 00 seconds of the next minute. Asyncio call_at应该能够做到这一点,但它使用的时间格式不是标准的 python 时间格式,我无法弄清楚将该时间格式指定为下一分钟的 00 秒。

import asyncio
import time


def callback(n, loop, msg):
    print(msg)
    print('callback {} invoked at {}'.format(n, loop.time()))


async def main(loop):
    now = loop.time()
    print('clock time: {}'.format(time.time()))
    print('loop  time: {}'.format(now))

    print('registering callbacks')
    loop.call_at(now + 0.2, callback, 1, loop, 'a')
    loop.call_at(now + 0.1, callback, 2, loop, 'b')
    loop.call_soon(callback, 3, loop, 'c')

    await asyncio.sleep(1)


event_loop = asyncio.get_event_loop()
try:
    print('entering event loop')
    event_loop.run_until_complete(main(event_loop))
finally:
    print('closing event loop')
    event_loop.close()

As others have noted, there is no built-in functionality for that kind of thing, you will need to write it yourself.正如其他人所指出的,这种事情没有内置功能,您需要自己编写。 It is straightforward to implement, though - a simple version could look like this:不过,实现起来很简单 - 一个简单的版本可能如下所示:

import asyncio, datetime

async def at_minute_start(cb):
    while True:
        now = datetime.datetime.now()
        after_minute = now.second + now.microsecond / 1_000_000
        if after_minute:
            await asyncio.sleep(60 - after_minute)
        cb()

This doesn't use call_later , it is a coroutine which can be canceled when no longer necessary.这不使用call_later ,它是一个可以在不再需要时取消的协程。 It simply takes the seconds value of the current wallclock time x , and sleeps (60 - x) to reach the next minute.它只是获取当前挂钟时间x的秒值,然后休眠(60 - x)以到达下一分钟。 Here is a test:这是一个测试:

import time
loop = asyncio.get_event_loop()
loop.create_task(at_minute_start(lambda: print(time.asctime())))
loop.run_forever()

# output:
Wed Feb 28 21:36:00 2018
Wed Feb 28 21:37:00 2018
Wed Feb 28 21:38:00 2018
...

Unfortunately, the simple implementation can misbehave if asyncio.sleep ever happens to sleep a tiny bit shorter than the requested period, eg due to clock skew.不幸的是,如果asyncio.sleep碰巧睡眠时间比请求的周期一点,例如由于时钟偏差,那么简单的实现可能会出错。 In that case the subsequent asyncio.sleep will try to again reach the start of the same minute and sleep for only a fraction of a second, resulting in the callback effectively firing twice in quick succession.在这种情况下,后续的asyncio.sleep将尝试再次到达同一分钟的开始,并且只休眠几分之一秒,从而导致回调有效地连续快速触发两次。 To prevent that, additional code is needed to compensate for the short sleeps:为了防止这种情况,需要额外的代码来补偿短暂的睡眠:

async def at_minute_start(cb):
    now = datetime.datetime.now()
    wait_for_next_minute = False
    while True:
        after_minute = now.second + now.microsecond / 1_000_000
        if after_minute != 0:
            to_next_minute = 60 - after_minute
        else:
            to_next_minute = 0  # already at the minute start
        if wait_for_next_minute:
            to_next_minute += 60
        await asyncio.sleep(to_next_minute)
        cb()
        prev = now
        now = datetime.datetime.now()
        # if we're still at the same minute, our sleep was slightly
        # too short, so we'll need to wait an additional minute
        wait_for_next_minute = now.minute == prev.minute

Like some commentators stated, there is no easy a resilient way to do this in pure Python with only asyncIO, but with the apscheduler library it becomes actually quite easy.就像一些评论员所说的那样,在纯 Python 中仅使用 asyncIO 没有简单的弹性方法来做到这一点,但是使用apscheduler库实际上变得非常容易。

import asyncio
import datetime
import os

from apscheduler.schedulers.asyncio import AsyncIOScheduler


def tick():
    print("Tick! The time is: %s" % datetime.datetime.now())


if __name__ == "__main__":
    scheduler = AsyncIOScheduler()
    scheduler.add_job(tick, "cron", minute="*")
    scheduler.start()
    print("Press Ctrl+{0} to exit".format("Break" if os.name == "nt" else "C"))

    # Execution will block here until Ctrl+C (Ctrl+Break on Windows) is pressed.
    try:
        asyncio.get_event_loop().run_forever()
    except (KeyboardInterrupt, SystemExit):
        pass

There's no simple way to do this.没有简单的方法可以做到这一点。 You can't rely on loop.time() as "real-world" clock.您不能依赖loop.time()作为“真实世界”时钟。

Only way around is to count the timedelta with datetime / time modules and call loop.call_later with calculated delay.唯一的解决方法是使用datetime / time模块计算loop.call_later并使用计算的延迟调用loop.call_later And yes, its super-cumbersome.是的,它超级麻烦。

Take a look at this question for examples: How can I periodically execute a function with asyncio?看看这个问题的例子: 如何使用 asyncio 定期执行函数?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM