1. なぜ async/await が必要か

通常のプログラム(同期処理)は、ファイルを読んだり HTTP リクエストを送ったりするとき、 結果が返ってくるまで その場で止まって待つ。 待っている間、CPU は何もしていない。

async/await を使うと「待つ」間に別の処理を進めることができる。 これを 非同期処理(asynchronous I/O) と呼ぶ。

同期処理 vs 非同期処理(時間軸) 同期 タスク A 実行 A 待機 A 再開→完了 タスク B 実行 B 待機 B 再開→完了 非同期 A A 実行 待機中 A 再開→完了 B B 実行(同時進行) 待機 B 再開→完了 時間 0 同期: 倍かかる
図 1. 同期処理では待機時間が連続するが、非同期処理では待機中に別タスクを進める

2. 基本構文

Python の非同期処理には 3 つのキーワードを使う。

キーワード 意味 場所
async def 非同期関数(コルーチン)を定義する 関数の先頭
await 「ここで待つ間、他の処理をどうぞ」と制御を手放す async def の内部のみ
asyncio.run() イベントループを起動して非同期関数を実行する プログラムの起点(if __name__ == "__main__" 等)
import asyncio

async def fetch_data(name: str) -> str:
    # 実際は HTTP リクエストや DB クエリなどが入る
    print(f"[{name}] 開始")
    await asyncio.sleep(1)  # "1秒待つ" のシミュレーション
    print(f"[{name}] 完了")
    return f"{name} のデータ"

async def main():
    result = await fetch_data("A")
    print(result)

asyncio.run(main())
注意: await asyncio.sleep() は通常の time.sleep() の非同期版。 time.sleep() を async 関数内で呼ぶと、イベントループ全体がブロックされてしまう。

3. 並行実行: asyncio.gather()

上のコードは A が終わってから次へ進む(順番待ち)。 本当の並行実行には asyncio.gather() を使う。

async def main():
    # 3 つを同時に開始し、全部終わるまで待つ
    results = await asyncio.gather(
        fetch_data("A"),
        fetch_data("B"),
        fetch_data("C"),
    )
    print(results)
    # → 約 1 秒で 3 つ分完了(順次なら約 3 秒かかる)
asyncio.gather() の動作イメージ イベントループ(asyncio の管理下) A A: 処理開始 await(I/O 待ち) A: 再開・完了 B B: 処理開始 await(待ち) B: 再開・完了 C C: 処理開始 await(待ち) C: 再開・完了 全完了
図 2. gather() はすべてのコルーチンを同時に開始し、最後の完了を待って結果をまとめて返す

4. コルーチンとイベントループの仕組み

async def で定義した関数を呼び出すと、実際には コルーチンオブジェクト が作られるだけで実行はされない。 実行は await するか asyncio.run() に渡した時に始まる。

await を書いた箇所で、その関数はイベントループに「制御」を渡す。 イベントループは待っている処理を管理し、I/O が終わったらその処理を再開させる。 これを 協調マルチタスク と呼ぶ(OS スレッドではなく、コード自身が制御を譲り合う)。

イベントループの内部サイクル イベント ループ 実行可能キュー コルーチン A (再開待ち) コルーチン C (再開待ち) ... I/O 待ちキュー コルーチン B (待機中) ネットワーク I/O... 取り出して 実行 await 発生 → I/O 登録 I/O 完了 → 実行可能キューへ移動
図 3. イベントループは実行可能なコルーチンを順に実行し、await を検出したら I/O 待ちキューに移す。I/O が終わると実行可能キューに戻す

5. よくある間違い

状況 NG OK
同期スリープを使う time.sleep(1) await asyncio.sleep(1)
await を忘れる fetch_data("A")
(コルーチンオブジェクトを返すだけ)
await fetch_data("A")
CPU バウンドな処理を async にする async def で重い計算をそのまま実行 asyncio.to_thread() でスレッドに委譲
順番待ちになっている await A, await B と逐次実行 asyncio.gather(A, B) で並行実行
async/await が向く場面・向かない場面
向く: HTTP クライアント、DB クエリ、ファイル I/O など I/O 待ちが多い処理
向かない: 画像処理・数値計算など CPU をフルに使う処理(この場合は multiprocessing を検討)

関連項目