Pythonのasyncioで非同期にリクエストを飛ばす

(2018-04-14)

Pythonのasyncioはイベントループを回してシングルスレッドで並行に非同期処理を行う。 マルチスレッドで並列に実行するのがthreadingで、 マルチプロセスで並列に実行するのがmultiprocessing

import asyncio

async def sleep(s):
    await asyncio.sleep(s)
    print(s)
    return s

loop = asyncio.get_event_loop()

loop.run_until_complete(sleep(5))

coros = [sleep(3), sleep(2)]
futures = asyncio.gather(*coros)
loop.run_until_complete(futures)
print(futures.result())
loop.close()
$ python main.py
5
2
3
[3, 2]

get_event_loop() でイベントループを取得し、 gather()で処理をまとめたりして、 run_until_complete()Futureの完了を待ち、 結果を取得してイベントループをclose()している。

async defを付けた関数はCoroutineとなり、 ensure_future()でFutureのサブクラスの、イベントループで実行させるTaskにすることができる。 run_until_complete()はそのままCoroutineを投げてもensure_future()でwrapしてくれる。

httpクライアントrequestsBlockingするようなので、asyncioに対応しているaiohttpを使ってリクエストしてみる。

import aiohttp
import asyncio
import async_timeout

async def fetch(session, url):
    print("{} start".format(url))
    async with async_timeout.timeout(10):
        async with session.get(url) as response:
            text = await response.text()
            print("{} done".format(url))
            return text

async def main():
    async with aiohttp.ClientSession() as session:
        urls = [
          'https://www.youtube.com',
          'https://www.python.org',
          'https://www.google.co.jp',
          'https://www.facebook.com'
        ]
        promises = [fetch(session, u) for u in urls]
        await asyncio.gather(*promises)

if __name__ == '__main__':
    loop = asyncio.get_event_loop()    
    loop.run_until_complete(main())

非同期にリクエストが飛び、返り次第処理が実行されている。

$ python req.py 
https://www.python.org start
https://www.facebook.com start
https://www.youtube.com start
https://www.google.co.jp start
https://www.python.org done
https://www.google.co.jp done
https://www.facebook.com done
https://www.youtube.com done