asyncioについて

はじめに

asyncioはasync/await構文を使い、並行処理のコードを書くためのライブラリです。

基本的な概念

  • イベントループ

    イベント(何らかのアクション)を待機するためのメカニズムです。

  • コルーチン

    途中で実行を止めることができる関数のようなものだと理解してください。

実践

コルーチンの実行

まずは、基本であるコルーチンを動かしてみましょう。

コルーチンの実行方法は3つありますが、Python3.6で書くと以下のようになります。
3.7以降では、asyncio.run()asyncio.create_task()ができる)

コルーチンの宣言は簡単で、通常の関数のように宣言を行えば良いです。

ただし、defの前にasyncを付けます。

コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import asyncio
async def f(n):
print (n, "start")
await asyncio.sleep(1)
print (n, "end")

async def g(n):
await f(n)

async def h(n):
loop = asyncio.get_event_loop()
task = loop.create_task(f(n))
await task

loop = asyncio.get_event_loop() # イベントループの生成
loop.run_until_complete(f(1)) # コルーチンをイベントループに直接渡す
loop.run_until_complete(g(2)) # コルーチンの内部でawaitコルーチンとしてイベントループに渡す
loop.run_until_complete(h(3)) # タスクとしてイベントループに渡す

startと出力し、1秒待ったのちendと出力するコルーチンfを定義し、3つの方法で実行しました。

タスク

さて、これだと通常の関数と比較した際のメリットが全く見えませんね。

そこでタスクを複数使って、非同期な処理を実装してみましょう。

コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import asyncio
async def f(n):
print (n, "start")
await asyncio.sleep(1)
print (n, "end")

async def g():
loop = asyncio.get_event_loop()
task1 = loop.create_task(f(1))
task2 = loop.create_task(f(2))
await task1
await task2

loop = asyncio.get_event_loop() # イベントループの生成
loop.run_until_complete(g()) # コルーチンの内部でawaitコルーチンとしてイベントループに渡す

実行結果を見るとわかるのですが、task1の終了を待たずしてtask2の実行が始まっていることが確認できます。

awaitとは

ここでawaitが何者なのか考えましょう。

先ほどのコルーチンgを書き換えます。

コード

1
2
3
4
5
6
async def g():
loop = asyncio.get_event_loop()
task1 = loop.create_task(f(1))
await task1
task2 = loop.create_task(f(2))
await task2

今度は期待するような非同期処理が行われなかったはずです。

これはtask1を作成した直後にawaitしてしまったためです。

awaitは処理が完了するまで、コルーチンやタスクをブロックします。つまり、完了するまでプログラムが進まないということになります。

ただし、何でもかんでもawaitをつけられるわけではなく、awaitableなオブジェクトにのみ付与が可能なので注意が必要です。

記事情報

  • 投稿日:2020年4月6日
  • 最終更新日:2020年5月4日