Tasks and coroutines のメモです。
Python で websockets を扱おうとしたらなぜか asyncio という物が必要になったので調べている。
Coroutine
コルーチンとは、javascript の async await のように、シングルスレッドで並行処理を行う物。
早速コルーチンを使ったカウンタを書いてみる。コルーチンを定義するには def
の代わりに async def
と書く。コルーチン関数を呼ぶと、コルーチン関数の内容はすぐ実行されず、コルーチンオブジェクトが返る。コルーチン関数の内容を実行するには、await を使って他のコルーチンから呼ぶか、イベントループに渡す必要がある。
import asyncio
# 1 から max までの数を数えるカウンターのコルーチン版
async def counter_coroutine(max):
count = 0
while count < max:
count += 1
print(count)
await asyncio.sleep(1) # asyncio.sleep は coroutine。await で完了を待つ。
return '数え終わりました'
counter = counter_coroutine(3) # コルーチン関数の返り値はコルーチンオブジェクト
print(f'counter is {counter}')
loop = asyncio.get_event_loop()
result = loop.run_until_complete(counter) # コルーチン関数の return で返した物は loop.run_until_complete の返り値になる。
print(result)
loop.close()
実行結果
counter is <coroutine object counter_coroutine at 0x1032f78e0>
1
2
3
数え終わりました
Future
コルーチンは上のように run_until_complete() に直接渡しても実行出来るが、実は run_until_complete() の内部的ではコルーチンから future を作って実行しているらしい。future を直接使えば多少冗長だがこのようになる。
import asyncio
# 1 から max までの数を数えて Future で結果を返す
async def counter_coroutine(max, future):
count = 0
while count < max:
count += 1
print(count)
await asyncio.sleep(1) # asyncio.sleep は coroutine。await で完了を待つ。
future.set_result('数え終わりました')
future = asyncio.Future() # future というのは非同期処理の結果を受け取る入れ物。
counter = counter_coroutine(3, future)
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(counter) # counter の実行を登録して task 生成(ここでは使わない)
print(f'task is {task}')
loop.run_until_complete(future)
print(future.result()) # set_result() の内容を result() で取り出せる。
loop.close()
実行結果
task is <Task pending coro=<counter_coroutine() running at future.py:4>>
1
2
3
数え終わりました
Task
上の例では、ensure_future によって counter の実行を登録した task という物を作りました。task はいわばスレッドのような物らしいです。task を何に使うかと言うと、このドキュメントを見た限り良くわかりませんでした。
複数の coroutine を並行に実行
asyncio.gather() を使うと、複数のコルーチンを並行に動かす事が出来る。
import asyncio
# 1 から max までの数をコルーチンで数える。名前付き。
async def counter_coroutine(name, max):
count = 0
while count < max:
count += 1
print(f'{name}: {count}')
await asyncio.sleep(1) # asyncio.sleep は coroutine。await で完了を待つ。
return f'{name} 数え終わりました'
# カウンターを3つ同時にスケジュール。
counters = asyncio.gather(
counter_coroutine('A', 1),
counter_coroutine('B', 2),
counter_coroutine('C', 3),
)
print(f'counters is {counters}') # asynio.gather の返り値は future
loop = asyncio.get_event_loop()
result = loop.run_until_complete(counters) # コルーチン関数の return で返した物は loop.run_until_complete の返り値になる。
print(result)
loop.close()
実行結果
counters is <_GatheringFuture pending>
C: 1
B: 1
A: 1
C: 2
B: 2
C: 3
['A 数え終わりました', 'B 数え終わりました', 'C 数え終わりました']
良くわからない事。
asyncio.ensure_future も asyncio.gather も同じようにコルーチンを受け取って実行を登録する関数だが、asyncio.ensure_future の返り値が task で asyncio.gather が future なのが謎。あと、全体的に複雑過ぎるような気がする。