79

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_futurewith loop.run_forever() to run multiple co-routines concurrently.

SamuelN
  • 1,341
  • 1
  • 10
  • 19
  • 4
    `run_until_complete` doesn't block anything. The difference between it and `run_forever` is that the loop pauses at the completion of the coroutine. The only time it will block is if your coroutine never awaits. – dirn Oct 20 '16 at 01:01
  • 1
    I wrote this http://pastebin.com/Qi8dQ3bh and it does seem to block the loop though. `do_other_things()` doesn't execute until `do_io()` is done, even though `do_io()` awaits a the sleep. – SamuelN Oct 20 '16 at 16:49
  • 4
    That's because nothing else has been scheduled with the loop. Try calling `loop.create_task(do_other_things())` before you call `run_forever`. – dirn Oct 20 '16 at 17:01

2 Answers2

95

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.

hdiogenes
  • 729
  • 7
  • 15
dirn
  • 19,454
  • 5
  • 69
  • 74
  • 3
    Do you know why I get the error "RuntimeError: This event loop is already running" when I run your code? – Patrick May 11 '18 at 20:31
  • @Patrick did you try calling `loop.run_until_complete` from inside a function? – dirn May 11 '18 at 20:38
  • 2
    I realized that the problem maybe with Jupyter. It works with python code. – Patrick May 14 '18 at 14:53
  • @Patrick (late) but I have scene this and used `loop = asyncio.new_event_loop()` (only one event loop on main is allowed though) – Ronald Petty Jul 18 '22 at 20:01
  • 1
    `get_event_loop` is deprecated : https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.get_event_loop – baxx Mar 23 '23 at 09:42
14

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
eastonsuo
  • 935
  • 9
  • 11
  • Nice, don't forget to import asyncio import time – Oriol Vilaseca Mar 21 '22 at 10:20
  • This answer is good because it uses modern Python 3.10+ asyncio.create_task/asyncio.run. Reducing it to just those two along with asyncio.gather, asyncio.sleep and async/await themselves simplifies things a lot. – 00prometheus Apr 07 '23 at 08:26