Python asyncio モジュール
asyncioは Python 標準ライブラリ内のモジュールで、非同期 I/O 操作のコードを記述するために使用されます。
asyncio は、並行タスクを処理するための効率的な方法を提供し、ネットワークリクエストやファイル読み書きなどの I/O 集中型操作に特に適しています。
使用することでasyncio、シングルスレッドで複数のタスクを同時に処理でき、マルチスレッドやマルチプロセスを使用する必要はありません。
なぜ asyncio が必要なのか?
従来の同期プログラミングでは、タスクが I/O 操作(ネットワークリクエストなど)の完了を待つ必要がある場合、プログラムは操作が完了するまでブロックされます。これにより、特に大量の I/O 操作を処理する必要がある場合、プログラムの効率が低下します。
asyncio非同期プログラミングモデルを導入することで、プログラムが I/O 操作の待機中に他のタスクを実行し続けることができ、それによってプログラムの並行性と効率が向上します。
あなたがレストランを経営していると想像してください:
- 同期モード(通常の関数):あなたには料理人が1人しかいません。客Aがステーキを注文し、料理人はステーキを焼き始めます(これには5分かかります)。この5分間、料理人は完全に占有され、他のことは何もできません。客Bがコップ一杯の水を注文したいだけでも、ただ待つしかありません。
- 非同期モード(asyncio):あなたには複数の料理人がいます(実際には1人だけですが、非常に賢いです)。料理人は客Aのステーキを焼き始めた後、待つ必要があることに気づくと、すぐにそのステーキを「準備中」とマークし、振り返って客Bに水を注ぎに行きます。水を注ぎ終えて戻ったら、ステーキが焼き上がったかどうかを確認し、まだ焼けていなければ、客Cの注文を処理することもできます。こうして、I/O(ステーキを焼く、ネットワークリクエスト、ファイルの読み書きなど)を待っている時間に、料理人(CPU)はずっと効率的に働いています。
asyncio は、この賢い作業モードを実現するために Python が提供する標準ライブラリです。シングルスレッド並行処理のコードを記述でき、Web クローラー、Web サーバー、マイクロサービスなどの I/O 集中型シナリオに特に適しています。
その中核は、イベントループ、コルーチン、そしてタスクです。
asyncio の中心概念
1. コルーチン(Coroutine)
コルーチンはasyncioの中心概念の一つです。これは特別な関数であり、実行中に一時停止し、後で実行を再開できます。コルーチンはasync defキーワードで定義され、awaitキーワードで実行を一時停止し、非同期操作の完了を待ちます。
例
async def say_hello():
print("Hello")
await asyncio.sleep(1)
print("World")
2. イベントループ(Event Loop)
イベントループはasyncioの中核コンポーネントであり、コルーチンのスケジューリングと実行を担当します。実行すべきタスクが常にあるかどうかをチェックし、タスク完了時に対応するコールバック関数を呼び出します。
例
await say_hello()
asyncio.run(main())
3. タスク(Task)
タスクはコルーチンをラップしたものであり、実行中またはこれから実行されるコルーチンを表します。あなたはasyncio.create_task()関数を使用してタスクを作成し、それをイベントループに追加できます。
例
task = asyncio.create_task(say_hello())
await task
4. Future
Futureは、非同期操作の結果を表すオブジェクトです。通常は低レベル API で使用され、まだ完了していない操作を表します。あなたはawaitキーワードでFuture完了を待ちます。
例
future = asyncio.Future()
await future
基本的な使い方とコード例
上記の概念を理解するために、複数のURLに並行アクセスする古典的な例を見てみましょう。
3つの異なるURLのコンテンツを取得する必要があるとします。同期方式では順番に実行され、総所要時間は3回のリクエスト時間の合計になります。asyncio、これら3つのリクエストを同時に送信でき、総所要時間は最も遅いリクエストにほぼ等しくなります。
同期バージョン(比較用)
例
import requests
def fetch_url(url):
"""時間のかかるネットワークリクエストをシミュレートする(同期版)"""
print(f"开始获取: {url}")
time.sleep(2) # 模拟 2 秒网络延迟
print(f"完成获取: {url}")
return f"{url} からのデータ"
def main_sync():
urls = ['https://example.com/1', 'https://example.com/2', 'https://example.com/3']
results = []
start = time.time()
for url in urls:
result = fetch_url(url) # 必须等上一个完成才能开始下一个
results.append(result)
end = time.time()
print(f"同步版本总耗时: {end - start:.2f} 秒")
print(f"结果: {results}")
if __name__ == "__main__":
main_sync()
期待される実行結果:
开始获取: https://example.com/1 完成获取: https://example.com/1 开始获取: https://example.com/2 完成获取: https://example.com/2 开始获取: https://example.com/3 完成获取: https://example.com/3 同步版本总耗时: 6.00 秒 结果: [‘来自 https://example.com/1 的数据‘, ‘来自 https://example.com/2 的数据‘, ‘来自 https://example.com/2 的数据‘]
合計で約6秒かかりました。
非同期バージョン(asyncio を使用)
私たちはaiohttpライブラリを使用して置き換え、requests非同期HTTPリクエストを実行します。まずそれをインストールします:pip install aiohttp。
例
import aiohttp
import time
async def fetch_url_async(session, url):
"""時間のかかるネットワークリクエストをシミュレートする(非同期版)"""
print(f"开始异步获取: {url}")
# 注意:ここでは aiohttp の非同期 get メソッドを使用し、await で待機します。
async with session.get(url) as response:
# 模拟处理响应也需要时间
await asyncio.sleep(2) # 使用 asyncio.sleep 模拟 I/O 等待,它不会阻塞线程
text = await response.text()
print(f"完成异步获取: {url}")
return f"{url} からのデータ(長さ:{len(text)})"
async def main_async():
urls = ['https://httpbin.org/get', 'https://httpbin.org/delay/1', 'https://httpbin.org/headers']
async with aiohttp.ClientSession() as session: # 创建异步 HTTP 会话
# URLごとにタスク(Task)を作成する
tasks = []
for url in urls:
# create_task 会将协程加入事件循环,立即开始调度
task = asyncio.create_task(fetch_url_async(session, url))
tasks.append(task)
print("所有任务已创建,开始并发执行...")
# asyncio.gather を使用してすべてのタスクを並行実行し、それらがすべて完了するのを待ちます
# gather は結果のリストを返します。順序は渡されたタスクの順序と一致します。
results = await asyncio.gather(*tasks)
return results
if __name__ == "__main__":
start = time.time()
# asyncio.run() はイベントループを起動し、トップレベルのコルーチンを実行する簡単な方法です
final_results = asyncio.run(main_async())
end = time.time()
print(f"\n"异步版本总耗时: {end - start:.2f} 秒")
for res in final_results:
print(res)
期待される実行結果:
所有任务已创建,开始并发执行... 开始异步获取: https://httpbin.org/get 开始异步获取: https://httpbin.org/delay/1 开始异步获取: https://httpbin.org/headers (大约 2 秒后,所有请求几乎同时完成) 完成异步获取: https://httpbin.org/headers 完成异步获取: https://httpbin.org/get 完成异步获取: https://httpbin.org/delay/1 异步版本总耗时: 2.10 秒 # 注意!总耗时远小于 6 秒 来自 https://httpbin.org/get 的数据 (长度: 274) 来自 https://httpbin.org/delay/1 的数据 (长度: 392) 来自 https://httpbin.org/headers 的数据 (长度: 177)
コード解説:
async def:コルーチン関数を定義しています。fetch_url_asyncとmain_async。await:`fetch` 内fetch_url_asyncでは、私たちはawait session.get()とawait response.text()、これはイベントループに「このネットワークリクエストは時間がかかるので、他の準備ができたタスクを先に実行して」と伝えています。asyncio.create_task():`fetch(url)` をfetch_url_asyncコルーチンとしてラップし、Task、イベントループによってスケジュールされ、並行処理を実現します。asyncio.gather(*tasks):非常に実用的な関数で、渡されたすべてのコルーチン/タスクを並行実行し、それらがすべて完了するのを待って、最終的にすべての結果を収集します。asyncio.run(main_async()):Python 3.7+ で推奨される方法で、イベントループの作成、コルーチンの実行、ループのクローズを担当します。
主要な関数とパラメータの説明
以下に表の形式でasyncioの中で最もよく使われる高級関数をいくつか示します:
| 関数 | 主な役割 | よく使うパラメータの説明 |
|---|---|---|
asyncio.run(coro, *, debug=False) |
トップレベルのコルーチンを実行し、イベントループのライフサイクルを管理します。プログラムのメインエントリです。 | coro:実行するコルーチンオブジェクト。debug:`True` に設定するとTrueイベントループのデバッグモードを有効にできます。 |
asyncio.create_task(coro, *, name=None) |
コルーチンをラップしてTaskオブジェクトにし、イベントループに登録してスケジュールを待ちます。これが並行処理を実現する主要な方法です。 | coro:ラップするコルーチンオブジェクト。name:(Python 3.8+)タスクに名前を指定して、デバッグを容易にします。 |
asyncio.gather(*aws, return_exceptions=False) |
並行実行複数の非同期タスク(awsコルーチン、タスクなどを受け入れます)、すべての完了を待って、結果のリストを返します。 |
*aws:可変長引数で、複数の非同期オブジェクトを渡します。return_exceptions:デフォルトはFalse、任意のタスクが例外をスローすると、すぐにgatherの呼び出し元に伝播します。`True` に設定した場合、True例外は結果の一部として返されます。 |
asyncio.sleep(delay, result=None) |
非同期的に指定秒数だけスリープします。これはtime.sleep(ブロッキング)との重要な違いです。 |
delayスリープする秒数。resultスリープ終了後に返される値。 |
asyncio.wait(aws, *, timeout=None, return_when=ALL_COMPLETED) |
タスクを並行実行し、指定した条件が満たされるのを待ちます。2つのコレクションを返します(done, pending)、それぞれ完了したタスクと未完了のタスクです。 |
aws: 非同期オブジェクトのコレクション。timeout: タイムアウト時間(秒)。return_when: いつ返すかを決定します。選択肢:FIRST_COMPLETED(最初に完了したとき)、FIRST_EXCEPTION(最初の例外が発生したとき)、ALL_COMPLETED(すべて完了したとき、デフォルト)。 |
asyncio.to_thread(func, /, *args, **kwargs) |
(Python 3.9+)通常の、ブロックする可能性のある同期関数を別のスレッドで実行し、await可能なawaitコルーチンを返します。CPU 密集型またはブロッキング I/O を処理するために使用します。 |
func: スレッドで実行する同期関数。*args, **kwargs: 関数に渡す引数。 |
視覚的に理解する:非同期タスクのスケジューリングの流れ

図解説明:
このフローチャートは、イベントループがどのようにスケジューラのように機能するかを示しています。イベントループはタスクキューを維持し、あるタスクがawait(例:ネットワーク応答の待機)に達すると、そのタスクは一時停止され、イベントループはすぐにキューから次に実行可能(準備完了)なタスクを見つけて実行します。一時停止中のタスクの I/O 操作が完了すると、イベントループは通知を受け取り、そのタスクの状態を準備完了に戻し、将来のある時点で実行を継続します。このようにして、I/O 待機中に CPU を他のタスクの実行に最大限活用し、シングルスレッド内での並行処理を実現します。
asyncio の基本的な使い方
1. コルーチンを実行する
コルーチンを実行するには、asyncio.run()関数を使用できます。この関数はイベントループを作成し、指定されたコルーチンを実行します。
例
async def main():
print("Start")
await asyncio.sleep(1)
print("End")
asyncio.run(main())
2. 複数のタスクを並行実行する
使用できるのはasyncio.gather()関数で複数のコルーチンを並行実行し、それらがすべて完了するのを待つことです。
例
async def task1():
print("Task 1 started")
await asyncio.sleep(1)
print("Task 1 finished")
async def task2():
print("Task 2 started")
await asyncio.sleep(2)
print("Task 2 finished")
async def main():
await asyncio.gather(task1(), task2())
asyncio.run(main())
3. タイムアウト制御
使用できるのはasyncio.wait_for()関数でコルーチンにタイムアウト時間を設定する方法です。指定した時間内にコルーチンが完了しない場合、asyncio.TimeoutError例外が発生します。
例
async def long_task():
await asyncio.sleep(10)
print("Task finished")
async def main():
try:
await asyncio.wait_for(long_task(), timeout=5)
except asyncio.TimeoutError:
print("Task timed out")
asyncio.run(main())
asyncio の適用シーン
asyncio特に以下のようなシーンに適しています:
- ネットワークリクエスト:HTTP リクエスト、WebSocket 通信など。
- ファイル I/O:ファイルの非同期読み書きなど。
- データベース操作:データベースへの非同期アクセスなど。
- リアルタイムデータ処理:リアルタイムメッセージキューの処理など。
よく使うクラス、メソッド、関数
1. コア関数
| メソッド/関数 | 説明 | 例 |
|---|---|---|
asyncio.run(coro) |
非同期メイン関数を実行(Python 3.7+) | asyncio.run(main()) |
asyncio.create_task(coro) |
タスクを作成してイベントループに追加 | task = asyncio.create_task(fetch_data()) |
asyncio.gather(*coros) |
複数のコルーチンを並行実行 | await asyncio.gather(task1, task2) |
asyncio.sleep(delay) |
非同期待機(非ブロッキング) | await asyncio.sleep(1) |
asyncio.wait(coros) |
タスクの完了方法を制御 | done, pending = await asyncio.wait([task1, task2]) |
2. イベントループ(Event Loop)
| メソッド | 説明 | 例 |
|---|---|---|
loop.run_until_complete(future) |
タスクが完了するまで実行 | loop.run_until_complete(main()) |
loop.run_forever() |
イベントループを永久に実行 | loop.run_forever() |
loop.stop() |
イベントループを停止 | loop.stop() |
loop.close() |
イベントループを閉じる | loop.close() |
loop.call_soon(callback) |
コールバック関数の即時実行を予約 | loop.call_soon(print, "Hello") |
loop.call_later(delay, callback) |
コールバックを遅延実行 | loop.call_later(5, callback) |
3. コルーチン(Coroutine)とタスク(Task)
| メソッド/デコレータ | 説明 | 例 |
|---|---|---|
@asyncio.coroutine |
コルーチンデコレータ(旧版、Python 3.4-3.7) | @asyncio.coroutine def old_coro(): |
async def |
コルーチンの定義(Python 3.5+) | async def fetch(): |
task.cancel() |
タスクをキャンセル | task.cancel() |
task.done() |
タスクが完了したかどうかを確認 | if task.done(): |
task.result() |
タスクの結果を取得(タスクの完了が必要) | data = task.result() |
4. 同期プリミティブ(類似:threading)
| 类 | 説明 | 例 |
|---|---|---|
asyncio.Lock() |
非同期ミューテックスロック | lock = asyncio.Lock() async with lock: |
asyncio.Event() |
イベント通知 | event = asyncio.Event() await event.wait() |
asyncio.Queue() |
非同期キュー | queue = asyncio.Queue() await queue.put(item) |
asyncio.Semaphore() |
セマフォ | sem = asyncio.Semaphore(5) async with sem: |
5. ネットワークとサブプロセス
| メソッド/クラス | 説明 | 例 |
|---|---|---|
asyncio.open_connection() |
TCP接続を確立 | reader, writer = await asyncio.open_connection('host', 80) |
asyncio.start_server() |
TCPサーバーを作成 | server = await asyncio.start_server(handle, '0.0.0.0', 8888) |
asyncio.create_subprocess_exec() |
サブプロセスを作成 | proc = await asyncio.create_subprocess_exec('ls') |
6. ユーティリティ
| メソッド | 説明 | 例 |
|---|---|---|
asyncio.current_task() |
現在のタスクを取得 | task = asyncio.current_task() |
asyncio.all_tasks() |
すべてのタスクを取得 | tasks = asyncio.all_tasks() |
asyncio.shield(coro) |
タスクをキャンセルから保護 | await asyncio.shield(critical_task) |
asyncio.wait_for(coro, timeout) |
タイムアウト付き待機 | try: await asyncio.wait_for(task, 5) |
例
基本的なコルーチンの例
例
async def hello():
print("Hello")
await asyncio.sleep(1)
print("World")
asyncio.run(hello()) # Python 3.7+
2. タスクの並行実行
例
print(f"Fetching {url}")
await asyncio.sleep(2)
return f"Data from {url}"
async def main():
results = await asyncio.gather(
fetch("url1.com"),
fetch("url2.com")
)
print(results)
asyncio.run(main())
3. 非同期キューの使用
例
for i in range(5):
await queue.put(i)
await asyncio.sleep(0.1)
async def consumer(queue):
while True:
item = await queue.get()
print(f"Consumed {item}")
queue.task_done()
async def main():
queue = asyncio.Queue()
await asyncio.gather(
producer(queue),
consumer(queue)
)
注意事項
-
Python バージョン:一部の機能は Python 3.7+ が必要です(例:
asyncio.run())。 -
ブロッキング操作:コルーチン内で同期ブロッキングコードを使用しないでください(例:
time.sleep())。 -
デバッグ:設定
PYTHONASYNCIODEBUG=1環境変数でデバッグモードを有効にできます。 -
タスクのキャンセル:キャンセルされたタスクは
CancelledErrorを発生させるため、適切に処理する必要があります。