Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to not await in a loop with asyncio?

Here is a toy example that downloads the home page from several websites using asyncio and aiohttp:

import asyncio
import aiohttp

sites = [
    "http://google.com",
    "http://reddit.com",
    "http://wikipedia.com",
    "http://afpy.org",
    "http://httpbin.org",
    "http://stackoverflow.com",
    "http://reddit.com"
]


async def main(sites):
    for site in sites:
        download(site)


async def download(site):
    response = await client.get(site)
    content = await response.read()
    print(site, len(content))


loop = asyncio.get_event_loop()
client = aiohttp.ClientSession(loop=loop)
content = loop.run_until_complete(main(sites))
client.close()

If I run it, I get:

RuntimeWarning: coroutine 'download' was never awaited

But I don't want to await it.

In twisted I can do:

for site in sites:
    download(site)

And If I don't explicitly "yield" or add a callback to the returned Deferred, it just runs without blocking nor complaining. I can't access the result, but in this case I don't need it.

In JS I can do:

site.forEarch(site){
    donwload(site)
}

And again, it doesn't block nor does it requires anything from my part.

I found a way to do:

async def main(sites):
    await asyncio.wait([download(site) for site in sites])

But:

  • this is really not obvious to find it out. I it's hard to remember.
  • it's hard to understand what it does. "waits" seems to say "i block", but does not convey clearly it block for the entire list of coroutine to finish.
  • you can't pass in a generator, it needs to be a real list, which i feels really unatural in Python.
  • what if I have only ONE awaitable ?
  • what if I don't want to wait at all on my tasks, and just schedule them for execution then carry on with the rest of my code ?
  • it's way more verbose thant twisted and JS solution.

It there a better way ?

like image 897
e-satis Avatar asked Dec 17 '15 16:12

e-satis


People also ask

How do I stop Asyncio from looping events?

Run an asyncio Event Loop run_until_complete(<some Future object>) – this function runs a given Future object, usually a coroutine defined by the async / await pattern, until it's complete. run_forever() – this function runs the loop forever. stop() – the stop function stops a running loop.

How do you start a coroutine without await?

Instead of await ing the coroutine, call asyncio. create_task to spawn a task object that runs in the background. At your next iteration you can check if the task is done and await/cancel it accordingly. (Otherwise asyncio will complain of un-awaited tasks being garbage collected.)

How do you stop a running event loop?

Run the event loop until stop() is called. If stop() is called before run_forever() is called, the loop will poll the I/O selector once with a timeout of zero, run all callbacks scheduled in response to I/O events (and those that were already scheduled), and then exit.

What does await do in Asyncio?

The keyword await passes function control back to the event loop. (It suspends the execution of the surrounding coroutine.) If Python encounters an await f() expression in the scope of g() , this is how await tells the event loop, “Suspend execution of g() until whatever I'm waiting on—the result of f() —is returned.


1 Answers

In order to schedule a coroutine as a task, use asyncio.ensure_future:

for site in sites:
    coro = download(site)
    future = asyncio.ensure_future(coro)

It replaces the deprecated function asyncio.async in version 3.4.4.

Then you can manage those futures using await, asyncio.wait or asyncio.gather.

like image 53
Vincent Avatar answered Sep 28 '22 05:09

Vincent