tabs ↹ over ␣ ␣ ␣ spaces

by Jiří {x2} Činčura

Playing with asyncio and aiohttp in Python as a C# developer

12 Oct 2017 Multithreading/Parallelism/Asynchronous/Concurrency, Python

Few months back I decided to play a little with asyncio in Python. Explore a little bit how “they did it” and align that in my mind with what I know about the implementation in .NET. Because at that time I needed some HTTP probing I decided to test the async IO together with regular HTTP (aka network) requests. And I used aiohttp. Not that I did some big research. I did quick search and looked at some demos to see whether I like how it’s structured or not. And aiohttp felt fine.

My script at the end looked like this (given it’s my first try, I would be happy if somebody corrects something suboptimal with my asyncio usage).

import sys
import time
import asyncio
import aiohttp
from termcolor import colored

urls = ["https://www.tabsoverspaces.com", "https://www.id3renamer.com"]

async def fetch(url, loop):
        async with aiohttp.ClientSession(loop=loop) as session:
                try:
                        start_time = time.perf_counter()
                        async with session.get(url, allow_redirects=False, timeout=10) as response:
                                end_time = time.perf_counter()
                                if response.status == 200:
                                        duration = (end_time - start_time) * 1000
                                        return (url, duration)
                                else:
                                        return (url, -1)
                except Exception:
                        return (url, -1)

async def main(loop):
        tasks = []
        for url in urls:
                tasks.append(asyncio.ensure_future(fetch(url, loop)))
        await asyncio.gather(*tasks)
        results = map(lambda x: x.result(), tasks)
        for fetch_result in results:
                (url, result) = fetch_result
                color = "green" if result != -1 else "red"
                print(colored("{0:<40}".format(url[0]), color) + "{0:.2f}ms".format(result))

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

First thing to test was whether it’s really asynchronous aka not creating any extra threads. Of course, it is. Otherwise I’m sure I wouldn’t be the first one to notice it.

The “magic” keywords are same as in C# - async and await. The async marks method where the await will be used and await is where the magic happens.

One thing to notice is the async with statement. The context manager needs to be able to suspend and resume the execution inside the with block.

The plumbing is done with asyncio, i.e. in C# we have Task.WhenAll and here it is asyncio.gather (although there’s also asyncio.wait with bunch of options).

What’s most interesting is the need to use event loop. In C# if you’re writing i.e. WinForms the concept of event loop (or message pumping in more low-level terms) is well known. But it’s not explicit and for example console applications don’t have such thing (even the abstraction - the SynchronizationContext - is null in console applications). Here, though my script is basically a console application, I had to specify it and specify it explicitly.

From what I understood the event loop is where the calls and most importantly callbacks (and hence resumes) happen. I think about it as a scheduler and IO completion ports (in Windows terms). My understanding is, I’m sure, clear on high level, but the rubber meets the road when you dive deep.

Although this was just a one or two afternoons playing with asyncio I enjoyed learning new stuff and applying it to what I already know. I wish I had more time to do this.