3.1 モジュール asyncio
自分のスレッドを非同期サブタスク用に作る人はもういないよ。 まあ、作ることはできるけど、そんなことするのはかなり低レベルだし、 フレームワーク開発者くらいしか使わないんだ。それもどうしても必要な時だけね。
今は流行なのは
非同期プログラミング、async/await
演算子
とコルーチン、そしてタスクだよ。でも、順を追って話そうか…
少し歴史を振り返ると
最初はPythonで非同期プログラミングの問題を解決するために
コルーチンが使われていたんだ、ジェネレーターに基づいたものね。
それから、Python 3.4でモジュール
asyncio
(たまにasync IO
と書かれることもある)が出てきて、
非同期プログラミングのメカニズムが実装されたんだ。
Python 3.5ではasync/await
という構文が追加されたんだよ。
さて、少し導入情報だよ。まずは、ざっとこれらについて話して、その後に詳しく、 さらに詳しく説明するね。どうしてもこれらは連携して動くから、 一つの要素を他と関連付けずに完全に説明するのは無理なんだ。
モジュール asyncio
モジュール asyncio
は
非同期プログラムを書くために使われていて、タスクの並列実行をサポートしているよ。
非同期のI/O操作、タイマー、ソケット、コルーチンの実行、
そして複数のイベントループでのマルチスレッドにも対応しているんだ。
コルーチン (Coroutines)
コルーチンは
非同期関数で、async def
というキーワードを使って定義されるよ。
コルーチンは
awaitキーワードを使って処理を一時停止できる
ので、
他のコルーチンがその間に実行されることができるんだよ
。
import asyncio
# 非同期関数(コルーチン)の定義
async def main():
print('Hello ...')
# 1秒間待つ
await asyncio.sleep(1)
print('... World!')
# イベントループで非同期関数main()を実行
asyncio.run(main())
イベントループ (Event Loop)
イベントループはコルーチン、タスク、他の非同期操作の実行を管理するよ。
asyncio.run()
の呼び出しはイベントループを開始し、
コルーチンを完了まで実行するんだ。
import asyncio
async def main():
print('Hello ...')
await asyncio.sleep(1)
print('... World!')
# 現在のイベントループを取得
loop = asyncio.get_event_loop()
# コルーチンを完了まで実行
loop.run_until_complete(main())
# 全てのタスク完了後にイベントループを閉じる
loop.close()
タスク (Tasks)
タスクを使うことでコルーチンを並行に実行できるんだ。
asyncio.create_task()
か
asyncio.ensure_future()
を使って作成するよ。
import asyncio
# 遅延で実行されるコルーチンの定義
async def say_after(delay, what):
# 指定した時間だけ待つ
await asyncio.sleep(delay)
print(what)
# メインのコルーチン
async def main():
# コルーチンを並行に実行するためにタスクを作成
task1 = asyncio.create_task(say_after(1, 'hello'))
task2 = asyncio.create_task(say_after(2, 'world'))
# 両方のタスクが完了するのを待つ
await task1
await task2
# メインのコルーチンを実行
asyncio.run(main())
フューチャー (Futures)
Future
オブジェクトは、非同期操作の結果を表していて、
将来的にアクセス可能になるんだ。例えば、非同期タスクの完了を待つときに使うよ。
import asyncio
# 長時間かかるタスクを模擬するコルーチンの定義
async def long_running_task():
print('Task started')
# 3秒間待つ
await asyncio.sleep(3)
print('Task finished')
return 'Result'
# メインのコルーチン
async def main():
# タスクの完了を待つためにフューチャーを作成
future = asyncio.ensure_future(long_running_task())
# タスクの完了を待って結果を取得
result = await future
print(f'Task result: {result}')
# メインのコルーチンを実行
asyncio.run(main())
3.2 非同期関数 — async def
非同期関数は普通の関数と同じように宣言されるんだけど、
def
の前にasync
って書くだけだよ。
async def 関数名(パラメータ):
関数のコード
非同期関数は普通の関数と同じように宣言されて、呼び出されるけど、 返す結果が違うんだ。非同期関数を呼び出すと、 結果ではなくて特別なオブジェクト、つまりコルーチンを返すんだよ。
これを実際に確認できるよ:
import asyncio
async def main():
print("Hello World")
# 非同期関数の呼び出し、コルーチンを返す
result = main()
# 結果の型を確認
print(type(result)) # <class 'coroutine'>
どうしてそうなるの?あなたが関数にasyncってつけると、 実際にはデコレーターを追加してることになって、それが大体こんなことをするんだよ:
def async_decorator(func):
# Taskオブジェクトを作成
task = Task()
# funcを渡して実行
task.target = func
# イベントループのタスクキューにtaskを追加
eventloop.add_task(task)
# taskオブジェクトを返す
return task
あなたのコードはこんな風になるんだ:
import asyncio
@async_decorator
def main():
print("Hello World")
result = main()
print(type(result)) # <class 'coroutine'>
このアナロジーの意味はこうだよ:
非同期関数を呼び出すと、特別なTask
オブジェクトが作成されて、
あなたの関数をあとで実行するんだ。0.0001秒後かもしれないし、10秒後かもしれない。
このtask
オブジェクトがあなたの非同期関数の呼び出しの結果としてすぐに返るんだ。これがコルーチンなんだよ。
あなたの非同期関数が実行されたかどうかはまだわからないけど、task
(コルーチン)オブジェクトはもう手元にある。
じゃあこのtask
(コルーチン)が何のためにあるの?使い道は少ないけど、3つのことが可能だよ:
- 非同期関数が実行されるのを待つ。
- 非同期関数が終わるのを待って、コルーチンから関数の結果を取得する。
- 10個(任意の数)の非同期関数が実行されるのを待つ。
どうやってやるのかは、後で教えるね。
3.3 演算子 await
コルーチンに関するアクションの大部分は「非同期関数の実行を待つ」から始まるんだ。
だからこそ、そのための特別な演算子await
があるんだよ。
ただコルーチンの前に書くだけだよ:
await コルーチン
または非同期関数の呼び出しのすぐ前に:
await 非同期_関数(引数)
Pythonがコード中で演算子await
を見つけると、現在の関数の実行を一時停止し、
コルーチンが実行されるまで、
つまりコルーチンが参照している非同期関数が終わるまで待つんだ。
重要!
演算子await
は非同期関数の内部で
他のコルーチンや非同期操作が完了するまで実行を一時停止するためにのみ使用されるよ。
これは、非同期関数の呼び出し間の切り替えを簡単にするために行われるよ。
await
の呼び出しは、実際には「どれくらい待つかわからないから、他の非同期関数の実行を進めてくれ」っていう宣言なんだ。
例:
import asyncio
# 非同期関数の定義
async def async_print(text):
print(text)
# メインの非同期関数
async def main():
# awaitを使って非同期関数の実行を待つ
await async_print("Hello World")
# メインのイベントループを実行し、コルーチンmain()を実行
asyncio.run(main()) #非同期関数を走らせる
実際、await
演算子はもう少し賢くて、非同期関数の実行結果も返してくれるんだ。
例:
import asyncio
# 2つの数字を足す非同期関数の定義
async def async_add(a, b):
return a + b
# メインの非同期関数
async def main():
# awaitを使ってasync_addの実行結果を取得
sum = await async_add(100, 200)
print(sum)
# メインのイベントループを実行し、コルーチンmain()を実行
asyncio.run(main()) #非同期関数を走らせる
じゃあ、まとめるね、演算子await
は:
- 現在の非同期関数を一時停止して、他のコルーチンや非同期操作が完了するまで待つ。
- 非同期操作やコルーチンの実行結果を返す。
- 非同期関数の中でのみ使用できる。
GO TO FULL VERSION