Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Running an Asyncio loop within a time interval

Goal: run main() consisting of a bunch of asyncio functions between start_time and end_time

import datetime as dt

start_time, end_time= dt.time(9, 29), dt.time(16, 20)

current_time() just keeps adding the current time to work space. This time is used at several different points in the script not shown here

async def current_time(): 
    while True:
        globals()['now'] = dt.datetime.now().replace(microsecond=0)
        await asyncio.sleep(.001)

Another function that does something:

async def balance_check():
    while True:    
        #do something here
        await asyncio.sleep(.001)

main() awaits the previous coroutines:

async def main():
    while True:
#Issue:This is my issue. I am trying to make these
#coroutines only run between start_time and end_time. 
#Outside this interval, I want the loop to 
#shut down and for the script to stop.
        if start_time < now.time() < end_time : 

            await asyncio.wait([
                    current_time(),
                    balance_check(),
                    ])
        else:
            print('loop stopped since {} is outside {} and {}'.format(now, start_time, end_time))
            loop.stop() 


loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
    loop.run_until_complete(main())
finally:
    loop.close()

Issue: this keeps working even after end_time

like image 524
Saeed Avatar asked Apr 29 '26 21:04

Saeed


1 Answers

The problem is incorrect use of await asyncio.wait([current_time(), balance_check(),]).

Awaiting asyncio.wait() waits for the specified awaitables to complete, i.e. either return a value or raise an exception. Since neither current_time nor balance_check ever return out of their infinite loops, the execution of main() never gets passed await asyncio.wait(...), as this expression waits for both to finish. In turn, the while loop in main() never gets to its second iteration, and loop.stop() has no chance to run.

If the code's intention was to use asyncio.wait() to give coroutines a chance to run, asyncio.wait is not the tool for that. Instead, one can just start the two tasks by calling asyncio.create_task(), and then do nothing. As long as the event loop can run (i.e. it's not blocked with a call to time.sleep() or similar), asyncio will automatically switch between the coroutines, in this case current_time and balanced_check at their ~1-millisecond pace. Of course, you will want to regain control by the end_time deadline, so "doing nothing" is best expressed as a single call to asyncio.sleep():

async def main():
    t1 = asyncio.create_task(current_time())
    t2 = asyncio.create_task(balance_check())
    end = dt.datetime.combine(dt.date.today(), end_time)
    now = dt.datetime.now()
    await asyncio.sleep((end - now).total_seconds())
    print('loop stopped since {} is outside {} and {}'.format(now, start_time, end_time))
    t1.cancel()
    t2.cancel()

Note that an explicit loop.stop() is not even necessary, because run_until_complete() will automatically stop the loop once the given coroutine completes. Calling cancel() on the tasks has no practical effect because the loop stops pretty much immediately; it is included to make those tasks complete, so that the garbage collector doesn't warn about destroying tasks that are pending.

As noted in the comments, this code doesn't wait for start_time, but that functionality is easily accomplished by adding another sleep before spawning the tasks.

like image 90
user4815162342 Avatar answered May 02 '26 09:05

user4815162342



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!