LoginSignup
11
10

More than 5 years have passed since last update.

Python asyncio

Posted at

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 なのが謎。あと、全体的に複雑過ぎるような気がする。

11
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
10