Python asyncio入門|非同期処理の基本からasync・awaitまでわかりやすく解説

Pythonで複数の処理を効率よく実行したいときに役立つのがasyncioです。 asyncioを使うと、ネットワーク通信やAPI呼び出しなど、待ち時間の多い処理を効率よく実行できます。

本記事では、Python初心者にもわかりやすいように、asyncioの基本から asyncawaitasyncio.gather()までをサンプルコード付きで解説します。

Pythonのasyncioとは?

asyncio(アシンキオ)は、Pythonで非同期処理を実装するための標準ライブラリです。

通常のPythonプログラムでは、ある処理が終わるまで次の処理に進みません。 一方、asyncioを利用すると、ネットワーク通信などの待ち時間に別の処理を進められます。

処理A ──待ち時間──┐
処理B ──待ち時間──┤→ 効率的に処理
処理C ──待ち時間──┘

特に、I/O待ちが発生する処理で効果を発揮します。

asyncioが向いている処理

asyncioは、以下のような処理と相性がよいです。

  • Web APIへのアクセス
  • HTTP通信
  • データベースへのアクセス
  • ソケット通信
  • ファイルI/O
  • 複数サイトからのデータ取得
  • 外部サービスとの通信

例えば、複数のWeb APIにアクセスする場合、それぞれのレスポンスを待っている時間を有効活用できます。

Python asyncioの基本

まずは最も基本的なasyncioプログラムを見てみましょう。

import asyncio

async def hello():
    print ("こんにちは")

asyncio.run (hello ())

実行すると、以下のように表示されます。

こんにちは

ここで重要なのがasync defです。 async defを使うことで、非同期関数(コルーチン関数)を定義できます。

asyncとは?

asyncは、関数を非同期処理として定義するために使用します。

async def hello():
    print ("Hello")

通常の関数ではdefを使用します。

def hello():
    print ("Hello")

非同期関数ではasync defを使用します。 ただし、async defで定義しただけでは処理は実行されません。

基本的にはawaitasyncio.run()を使って実行します。

awaitとは?

awaitは、非同期処理の完了を待つために使用します。

import asyncio

async def hello():
    print ("開始")
    await asyncio.sleep (1)
    print ("終了")

asyncio.run (hello ())

実行すると、次のような流れになります。

開始
1秒待つ
終了

await asyncio.sleep (1)によって1秒間待機します。 この待機中に、イベントループは別の処理を進めることができます。

asyncio.sleep()を使ってみる

asyncio.sleep()は、asyncioを学習する際によく使われる関数です。

import asyncio

async def main():
    print ("開始")

    await asyncio.sleep (2)

    print ("2秒後")

asyncio.run (main ())

通常のtime.sleep()とは異なり、 await asyncio.sleep (2)ではイベントループに制御を戻しながら待機できます。

asyncio.run()とは?

asyncio.run()は、非同期関数を実行するときに利用する代表的な関数です。

import asyncio

async def main():
    print ("Python asyncio")

asyncio.run (main ())

Pythonでasyncioを始めるなら、 asyncio.run (main ())という基本パターンを覚えておくとよいでしょう。

asyncioで複数の処理を実行する

asyncioの大きなメリットは、複数の非同期処理を効率的に実行できることです。

import asyncio

async def task1():
    print ("タスク1開始")
    await asyncio.sleep (2)
    print ("タスク1終了")

async def task2():
    print ("タスク2開始")
    await asyncio.sleep (2)
    print ("タスク2終了")

async def main():
    await task1 ()
    await task2 ()

asyncio.run (main ())

この場合は、タスク1が終了してからタスク2が開始されるため、 合計で約4秒かかります。

asyncio.gather()で複数の処理を実行する

複数の非同期処理をまとめて実行するには、 asyncio.gather()が便利です。

import asyncio

async def task1():
    print ("タスク1開始")
    await asyncio.sleep (2)
    print ("タスク1終了")

async def task2():
    print ("タスク2開始")
    await asyncio.sleep (2)
    print ("タスク2終了")

async def main():
    await asyncio.gather (
        task1 (),
        task2 ()
    )

asyncio.run (main ())

この場合、2つのタスクが同時進行するため、 全体として約2秒で終了します。

タスク1 ───── 2秒 ───── 完了
タスク2 ───── 2秒 ───── 完了

ただし、これはCPU処理そのものが高速化しているわけではありません。 待ち時間を有効活用していることがポイントです。

asyncio.gather()の基本構文

基本的な構文は以下のとおりです。

await asyncio.gather (
    task1 (),
    task2 (),
    task3 ()
)

複数の非同期処理をまとめて実行できます。

import asyncio

async def download1():
    await asyncio.sleep (1)
    return "データ1"

async def download2():
    await asyncio.sleep (2)
    return "データ2"

async def download3():
    await asyncio.sleep (1)
    return "データ3"

async def main():
    results = await asyncio.gather (
        download1 (),
        download2 (),
        download3 ()
    )

    print (results)

asyncio.run (main ())

結果は以下のようになります。

['データ1', 'データ2', 'データ3']

asyncioと同期処理の違い

同期処理と非同期処理の違いを理解すると、asyncioのメリットがわかりやすくなります。

同期処理

import time

def task1():
    time.sleep (2)
    print ("task1")

def task2():
    time.sleep (2)
    print ("task2")

task1 ()
task2 ()

この処理は約4秒かかります。

非同期処理

import asyncio

async def task1():
    await asyncio.sleep (2)
    print ("task1")

async def task2():
    await asyncio.sleep (2)
    print ("task2")

async def main():
    await asyncio.gather (
        task1 (),
        task2 ()
    )

asyncio.run (main ())

こちらは2つの待機処理が同時進行するため、約2秒で終了します。

asyncioはCPU負荷の高い処理には向いていない

asyncioを使えば、どんな処理でも高速になるわけではありません。

例えば、以下のようなCPU負荷の高い処理です。

for i in range (100000000):
    # 重い計算
    pass

このような処理は、asyncioよりも用途に応じて multiprocessing、ProcessPoolExecutor、NumPy、GPU処理などを検討したほうがよい場合があります。

asyncioは基本的にI/O待ちの効率化が得意と考えるとわかりやすいでしょう。

asyncio.create_task()とは?

asyncio.create_task()を使うと、 コルーチンをタスクとしてスケジュールできます。

import asyncio

async def task (name):
    print (f"{name}開始")
    await asyncio.sleep (2)
    print (f"{name}終了")

async def main():
    task1 = asyncio.create_task (task ("タスク1"))
    task2 = asyncio.create_task (task ("タスク2"))

    await task1
    await task2

asyncio.run (main ())

create_task()を使うことで、 複数の処理をイベントループ上で進行させることができます。

asyncio.gather()とcreate_task()の違い

asyncioを学習していると、gather()create_task()の違いが気になるでしょう。

方法 特徴
await 非同期処理の完了を待つ
asyncio.gather() 複数の処理をまとめて実行する
asyncio.create_task() コルーチンをタスクとしてスケジュールする
asyncio.run() 非同期処理の入口として実行する

複数の処理をまとめて実行したいだけなら、 まずはasyncio.gather()を覚えるとよいでしょう。

Python asyncioでAPI通信を効率化する

asyncioが活躍する代表例がAPI通信です。

複数のAPIへアクセスする場合、同期処理ではそれぞれのレスポンスを順番に待つことになります。

API A → 待つ
       ↓
API B → 待つ
       ↓
API C → 待つ

非同期処理では、複数の通信を同時進行させることができます。

API A ─────────┐
API B ─────────┼→ 結果を取得
API C ─────────┘

実際のHTTP通信では、非同期HTTPクライアントとして aiohttpなどを利用する方法があります。

import asyncio
import aiohttp

async def fetch (session, url):
    async with session.get (url) as response:
        return await response.text ()

async def main():
    urls = [
        "https://example.com",
        "https://example.org",
        "https://example.net"
    ]

    async with aiohttp.ClientSession () as session:
        results = await asyncio.gather (
            *(fetch (session, url) for url in urls)
        )

        for result in results:
            print (len (result))

asyncio.run (main ())

このように、複数のURLへ効率よくアクセスできます。

asyncioでよくあるエラー

RuntimeWarning: coroutine was never awaited

asyncio初心者が遭遇しやすいエラーの一つです。

async def hello():
    print ("Hello")

hello ()

非同期関数を呼び出しただけで、awaitしていません。

非同期関数の中なら、次のようにします。

await hello ()

また、プログラムの入口ではasyncio.run()を使用できます。

asyncio.run (hello ())

asyncio.run() cannot be called from a running event loop

Jupyter Notebookなど、すでにイベントループが動いている環境では、 asyncio.run()でエラーになることがあります。

その場合、環境に応じて以下のように直接awaitする方法があります。

await main ()

Python asyncioを学ぶときのポイント

asyncio初心者は、いきなり複雑な非同期プログラムを書く必要はありません。 次の順番で学習すると理解しやすくなります。

1. asyncを理解する

async def main():
    pass

2. awaitを理解する

await asyncio.sleep (1)

3. asyncio.run()を理解する

asyncio.run (main ())

4. asyncio.gather()を理解する

await asyncio.gather (
    task1 (),
    task2 ()
)

5. create_task()を理解する

task = asyncio.create_task (task_function ())

6. API通信などで実践する

この順番で学ぶと、asyncioの仕組みを段階的に理解できます。

asyncioとthreadingの違い

Pythonで並行処理を考えるとき、asyncioとthreadingの違いも重要です。

項目 asyncio threading
方式 非同期処理 スレッド
得意分野 I/O処理 I/O処理など
並行処理 イベントループ 複数スレッド
コード async / await Threadなど
主な用途 大量のI/O処理 I/O処理・既存コードとの組み合わせなど

asyncioを使うメリット

1. I/O待ちを有効活用できる

ネットワーク通信などの待ち時間に別の処理を進められます。

2. 大量の通信処理と相性がよい

APIやWebサイトなど、複数の外部サービスへアクセスするプログラムに向いています。

3. Python標準ライブラリで利用できる

基本的な非同期処理は、Python標準ライブラリのasyncioで実装できます。

4. async/awaitでコードを書ける

非同期処理を比較的読みやすい形で記述できます。

asyncioを使う際の注意点

asyncioを使う場合、すべての関数をasyncにすればよいわけではありません。

特に注意したいのがブロッキング処理です。

例えば、以下のような処理です。

time.sleep (5)

このような処理を非同期処理の中で実行すると、 イベントループを止めてしまう可能性があります。

非同期処理で待機する場合は、基本的に以下のようにします。

await asyncio.sleep (5)

また、同期的なライブラリを非同期コードに組み込む場合にも注意が必要です。

Python asyncio入門まとめ

Pythonのasyncioは、非同期処理を実装するための標準ライブラリです。

特に以下のような処理で力を発揮します。

  • API通信
  • Web通信
  • データベースアクセス
  • ソケット通信
  • 外部サービスとの連携
  • 大量のI/O処理

asyncioを学ぶうえでは、まず以下の4つを押さえておきましょう。

async def
await
asyncio.run ()
asyncio.gather ()

基本的な使い方をまとめると、以下のようになります。

import asyncio

async def task (name):
    print (f"{name}開始")
    await asyncio.sleep (2)
    print (f"{name}終了")

async def main():
    await asyncio.gather (
        task ("タスク1"),
        task ("タスク2")
    )

asyncio.run (main ())

asyncioを理解するうえで重要なのは、 「待っている時間に別の処理を進める」という考え方です。

特にPythonでAPI連携、Webスクレイピング、AIサービスとの連携などを開発する場合、 asyncioを理解しておくと、より効率的なプログラムを設計できるようになります。

よくある質問(FAQ)

Pythonのasyncioとは何ですか?

asyncioは、Pythonで非同期処理を実装するための標準ライブラリです。 特にネットワーク通信など、I/O待ちが発生する処理で効果を発揮します。

asyncとawaitの違いは何ですか?

asyncは非同期関数を定義するために使い、 awaitは非同期処理の完了を待つために使用します。

asyncioはCPU処理の高速化にも使えますか?

asyncioは主にI/O待ちの効率化に向いています。 CPU負荷の高い計算処理の高速化には、multiprocessingなど別の方法を検討する必要があります。

asyncio.gather()とは何ですか?

複数の非同期処理をまとめて実行し、結果をまとめて取得するための関数です。

Python初心者でもasyncioを使えますか?

はい。まずはasync defawaitasyncio.run()asyncio.gather()の4つから学ぶと理解しやすくなります。

プログラミング学習に興味がある方は、まずは無料体験会へ

無料体験会・お申し込みはこちら