Why do most asyncio examples use loop.run_until_complete()?
I was going through the Python documentation for asyncio
and I'm wondering why most examples use loop.run_until_complete()
as opposed to Asyncio.ensure_future()
.
For example: https://docs.python.org/dev/library/asyncio-task.html
It seems ensure_future
would be a much better way to demonstrate the advantages of non-blocking functions. run_until_complete
on the other hand, blocks the loop like synchronous functions do.
This makes me feel like I should be using run_until_complete
instead of a combination of ensure_future
with loop.run_forever()
to run multiple co-routines concurrently.
Solution 1:
run_until_complete
is used to run a future until it's finished. It will block the execution of code following it. It does, however, cause the event loop to run. Any futures that have been scheduled will run until the future passed to run_until_complete
is done.
Given this example:
import asyncio
async def do_io():
print('io start')
await asyncio.sleep(5)
print('io end')
async def do_other_things():
print('doing other things')
loop = asyncio.get_event_loop()
loop.run_until_complete(do_io())
loop.run_until_complete(do_other_things())
loop.close()
do_io
will run. After it's complete, do_other_things
will run. Your output will be:
io start
io end
doing other things
If you schedule do_other_things
with the event loop before running do_io
, control will switch from do_io
to do_other_things
when the former awaits.
loop.create_task(do_other_things())
loop.run_until_complete(do_io())
This will get you the output of:
doing other things
io start
io end
This is because do_other_things
was scheduled before do_io
. There are a lot of different ways to get the same output, but which one makes sense really depends on what your application actually does. So I'll leave that as an exercise to the reader.
Solution 2:
I think most people didn't understand create_task
.
when you create_task
or ensure_future
, it will be scheduled already.
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
task1 = asyncio.create_task(
say_after(1, 'hello')) # not block here
task2 = asyncio.create_task(
say_after(2, 'world'))
print(f"started at {time.strftime('%X')}") # time0
await task1 # block here!
print(f"finished at {time.strftime('%X')}")
await task2 # block here!
print(f"finished at {time.strftime('%X')}")
asyncio.run(main())
result is
time0
print hello
time0+1
print world
time0+2
but IF YOU DON'T AWAIT task1, do something else
async def main():
task1 = asyncio.create_task(
say_after(1, 'hello')) # not block here
print(f"finished at {time.strftime('%X')}") # time0
await asyncio.sleep(2) # not await task1
print(f"finished at {time.strftime('%X')}") # time0+2
asyncio.run(main())
it will do task1 STILL
time0
print hello
time0+2