pythonpython-3.xasync-awaitfuturepython-asyncio

Combine awaitables like Promise.all


In asynchronous JavaScript, it is easy to run tasks in parallel and wait for all of them to complete using Promise.all:

async function bar(i) {
  console.log('started', i);
  await delay(1000);
  console.log('finished', i);
}

async function foo() {
    await Promise.all([bar(1), bar(2)]);
}

// This works too:
async function my_all(promises) {
    for (let p of promises) await p;
}

async function foo() {
    await my_all([bar(1), bar(2), bar(3)]);
}

I tried to rewrite the latter in python:

import asyncio

async def bar(i):
  print('started', i)
  await asyncio.sleep(1)
  print('finished', i)

async def aio_all(seq):
  for f in seq:
    await f

async def main():
  await aio_all([bar(i) for i in range(10)])

loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()

But it executes my tasks sequentially.

What is the simplest way to await multiple awaitables? Why doesn't my approach work?


Solution

  • The equivalent would be using asyncio.gather:

    import asyncio
    
    async def bar(i):
      print('started', i)
      await asyncio.sleep(1)
      print('finished', i)
    
    async def main():
      await asyncio.gather(*[bar(i) for i in range(10)])
    
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())
    loop.close()
    

    Why doesn't my approach work?

    Because when you await each item in seq, you block that coroutine. So in essence, you have synchronous code masquerading as async. If you really wanted to, you could implement your own version of asyncio.gather using loop.create_task or asyncio.ensure_future.

    EDIT

    The original answer used the lower-level asyncio.wait.