はじめに
最近PythonのAPIフレームワークであるFastAPIの学習を始めました。
そこで頻繁にasync、awaitというキーワードを頻繁に使うので学習も兼ねてここにまとめます。
同期処理
Pythonでは通常、関数やメソッドを呼び出すと、呼び出した先の処理が全て終わるまで、呼び出し元は次の処理に進めません。このような処理は、一般的に同期処理と呼ばれます。
非同期処理
一方で非同期処理では、呼び出し先の処理が実行中であっても、呼び出し元は次の処理に進められます。呼び出した先の結果が得られる前に呼び出し元は先に進んでいるため、処理の完了通知や結果はコールバック関数などで呼び出し元に伝えられます。
コルーチン
asyncioモジュールにはコルーチンという技術で非同期処理を実現しています。コルーチンは処理の途中で中断、再開できる性質を持っています。
例えば、Web APIを利用するコルーチンがあるとして、このコルーチン内でHTTPリクエストを送信するとレスポンスが返ってくるまでは次の処理に進めません。このような時にコルーチンを中断させることで、別のコルーチンを動かすことにCPUリソースを活用できる。
async awaitの使い方
Pythonでコルーチンを定義するには関数定義のdefをasync defにするだけです。
そしてawait文でコルーチンの呼び出しと中断を行います。
import asyncio
import random
async def send_http_request(url):
print(f'リクエストを送信: {url}')
# WebAPIの処理をスリープで代用
await asyncio.sleep(random.randint(1, 5))
print(f'レスポンスを受信: {url}')
async def async_download(url):
res = await send_http_request(url)
return res
async def main():
task = asyncio.gather(
async_download('https://qiita.com'),
async_download('https://ja-jp.facebook.com'),
async_download('https://twitter.com'),
async_download('https://www.youtube.com'),
)
return await task
asyncio.run(main())
上記のコードを実行すると以下のような結果が出ます。
リクエストを送信: https://qiita.com
リクエストを送信: https://ja-jp.facebook.com
リクエストを送信: https://twitter.com
リクエストを送信: https://www.youtube.com
------------------------------------------
レスポンスを受信: https://twitter.com
レスポンスを受信: https://ja-jp.facebook.com
レスポンスを受信: https://qiita.com
レスポンスを受信: https://www.youtube.com
まず、gather()関数の引数に渡した最初のコルーチンが実行されます。その処理中に、asyncio.sleep()まで進むと処理が中断され、2番目のコルーチンが動き始めます。asyncio.sleep()で処理が中断されるのは、I/O処理による待ち時間が発生するためです。同様に2番目のコルーチンもasyncio.sleep()まで進むと処理が中断され、三番目が動き始めます。4番目も同様に進み、レスポンスが返ってくるまで待機されます。そのあと、レスポンスが返ってきて再開可能になったコルーチンから順次処理が再開されています
非同期処理をいつ使うか
データベースへの接続、WebAPIの利用など通信による待ち時間が発生する時など。