Reputation: 2109
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
Upvotes: 3
Views: 3699
Reputation: 155630
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.
Upvotes: 2
Reputation: 8055
You could use synchronizing primitives' to do this, specifically events
.
import datetime as dt
import asyncio
async def function(start_event, end_event):
# Waits until we get the start event command.
await start_event.wait()
# Run our function until we get the end_event being set to true.
index = 0
while not end_event.is_set():
print(f"Function running; index {index}.")
await asyncio.sleep(2)
index += 1
async def controller(start_event, end_event):
# Will usually be this. Commented out for testing purposes. Can be passed by main.
# Subtraction/addition does not work on dt.datetime(). Inequality does (> or <)
# now, start, end = dt.datetime.now().time(), dt.time(17, 24), dt.time(18, 0, 30)
now = dt.datetime.now()
start, end = now + dt.timedelta(seconds=-10), now + dt.timedelta(seconds=10)
# Starts our function.
print(f"Starting at {start}. Ending in {end - now}. ")
start_event.set()
# Keeps checking until we pass the scheduled time.
while start < now < end:
print(f"Ending in {end - now}.")
await asyncio.sleep(2)
now = dt.datetime.now()
# Ends our function.
end_event.set()
print(f"Function ended at {now}.")
async def main():
# Creates two indepedent events.
# start_event is flagged when controller is ready for function to be ran (prevents misfires).
# end_event is flagged when controller see's start < time.now < end (line 30).
start_event = asyncio.Event()
end_event = asyncio.Event()
# Starts both functions at the same time.
await asyncio.gather(
controller(start_event, end_event), function(start_event, end_event)
)
if __name__ == "__main__":
# Starting our process.
asyncio.run(main())
This method will require both function
and controller
to take up space in the asyncio
loop. However, function
will be locked for most of the time, and it will instead be the controller hogging loop resources with its while
loop, so keep that in the back of your mind.
Upvotes: 1