Reputation: 7151
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 (i.e. I want to run it at the start of every minute, so at 19:00:00, 19:01:00, etc..).
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.
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()
Upvotes: 7
Views: 5025
Reputation: 7151
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.
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
Upvotes: 6
Reputation: 154856
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. It simply takes the seconds value of the current wallclock time x
, and sleeps (60 - x)
to reach the next minute. 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, e.g. due to clock skew. 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. 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
Upvotes: 8
Reputation: 8572
There's no simple way to do this. You can't rely on loop.time()
as "real-world" clock.
Only way around is to count the timedelta with datetime
/time
modules and call loop.call_later
with calculated delay. And yes, its super-cumbersome.
Take a look at this question for examples: How can I periodically execute a function with asyncio?
Upvotes: 1