Pythonのasync
とawait
キーワードは非同期タスクを管理するために使用されます。
非同期プログラミングは、他のタスクをブロックすることなく複数のタスクを同時に処理できるようにします。Pythonでは、コルーチンを使用してこれを実現します。
async
キーワードは、関数を非同期コルーチンとして定義するために使用され、await
キーワードはコルーチン関数内で別のコルーチンの実行を待つために使用されます。
コルーチンとは何か?
コルーチンは、特定のポイントで実行を一時停止し、再開する機能を持つ通常の関数とほぼ同じです。これにより、非ブロッキング動作が可能になり、コルーチンの実行が遅延した場合や、他のコードを実行するために意図的に実行を一時停止することができます。
コルーチンはジェネレータ関数と非常に密接に関連しています。実際、Python 3.5でasync
とawait
が導入される以前は、ジェネレータ関数を特別な方法で使用してコルーチンを実装していました。
Python 3.5以降のバージョンでは、async
とawait
という2つのキーワードを使用して、コルーチンをより便利かつネイティブに作成できるようになりました。
-
async
はコルーチン関数を作成します。 -
await
は他のコルーチンを実行するためにコルーチンを一時停止させます。
async
を使ってコルーチン関数を定義する
コルーチン関数の定義は、通常の関数の定義に非常に似ています。次の例を考えてみましょう。
例: 通常の関数を定義する
def add(a, b):
print(f'{a} + {b} = {a + b}')
通常の関数では、宣言時にdef
キーワードを使用しますが、コルーチン関数の場合はdef
の代わりにasync def
を使用します。
例: コルーチン関数を定義する
async def add(a, b):
print(f'{a} + {b} = {a + b}')
コルーチン関数を呼び出す
通常の関数を呼び出すには、その名前の後に必要な引数を括弧で囲んで使用します。
例: 通常の関数を呼び出す
def add(a, b):
print(f'{a} + {b} = {a + b}')
add(10, 20)
出力:
10 + 20 = 30
通常の関数とは異なり、コルーチン関数を呼び出しても、すぐに関数内のコードが実行されるわけではありません。代わりに、コルーチンオブジェクトが作成されます。
例:
async def add(a, b):
print(f'{a} + {b} = {a + b}')
# コルーチンを呼び出す
coro = add(10, 20) # コルーチンオブジェクトを作成する
print(coro)
出力:
<coroutine object add at 0x7f55ab5b3c40>
コルーチン関数内のコードを実行するには、イベントループと呼ばれるもので待機する必要があります。最も直接的な方法は、標準ライブラリのasyncio
モジュールのrun()
関数を使用することです。
例:
import asyncio
async def add(a, b):
print(f'{a} + {b} = {a + b}')
# コルーチンオブジェクトを作成する
coro = add(10, 20)
# コルーチンを実行する
asyncio.run(coro) # コルーチン内のコードを実行する
出力:
10 + 20 = 30
asyncio.run()
関数はイベントループを作成し、指定されたコルーチンを実行してその結果を返します。これは非同期アプリケーションのメイン関数を実行するため、または単一のコルーチンを実行するために設計されています。
asyncio.run()
を使用しない場合、イベントループを手動で初期化および管理し、タスクを作成およびスケジュールし、コードをコルーチンでラップする必要があり、これに比べるとasyncio.run()
を使用する方が複雑さやエラーの発生リスクが低くなります。
例: 他の例
import asyncio
async def add(a, b):
print(f'{a} + {b} = {a + b}')
asyncio.run(add(50, 30))
asyncio.run(add(50, 50))
asyncio.run(add(90, 10))
出力:
50 + 30 = 80
50 + 50 = 100
90 + 10 = 100
awaitでコルーチンを一時停止させる
await
キーワードは、別の内部コルーチンが終了するまでコルーチンの実行を一時停止するために使用されます。待機しているコルーチンが終了すると、元のコルーチンは中断した場所から実行を再開します。
構文:
await <coro()>
await
キーワードは、待機するコルーチンオブジェクトcoro()
を1つだけ引数に取ります。
次の例を考えてみましょう。
例: コルーチンを待機する
import asyncio
# 0からnまでの偶数を表示する
async def display_evens(n):
for i in range(n):
if i % 2 == 0:
print(i)
async def main():
print("Started!")
await display_evens(10) # await display_evens()
print('Finished!')
# メインを実行する
asyncio.run(main())
出力:
Started!
0
2
4
6
8
Finished!
上記の例では、await
文に遭遇したとき、main
の実行はdisplay_evens()
コルーチンが完全に実行されるまで一時停止します。必要に応じて複数のコルーチンを待機することができます。
例: 複数のコルーチンを待機する
import asyncio
# 0からnまでの偶数を表示する
async def display_evens(n):
for i in range(n):
if i % 2 == 0:
print(i)
await asyncio.sleep(0.01) # 少しの遅延を発生させる
# 0からnまでの奇数を表示する
async def display_odds(n):
for i in range(n):
if i % 2 == 1:
print(i)
await asyncio.sleep(0.01) # 少しの遅延を発生させる
async def main():
print("Started!")
await display_evens(10)
await display_odds(10)
print('Finished!')
# メインを実行する
asyncio.run(main())
出力:
Started!
0
2
4
6
8
1
3
5
7
9
Finished!
上記の例では、await
されたコルーチンdisplay_evens()
とdisplay_odds()
は、順番に実行されます。次のセクションでは、await
されたコルーチンを同時に実行する方法を見てみましょう。
await
されたコルーチンを並行して実行する
非同期プログラミングの主な目標は、複数のタスクを並行して実行することです。これにより、あるタスクが実行されている間に、他のタスクも同時に開始し実行できます。
複数のコルーチンを並行して非ブロッキング方式で実行するには、asyncio.gather()
関数を使用して1つのawaitable
オブジェクトに結合し、次にそのオ
ブジェクトをawait
します。
例: 複数のコルーチンを実行する
import asyncio
# 0からnまでの偶数を表示する
async def display_evens(n):
for i in range(n):
if i % 2 == 0:
print(i)
await asyncio.sleep(0.01) # 少しの遅延を発生させる
# 0からnまでの奇数を表示する
async def display_odds(n):
for i in range(n):
if i % 2 == 1:
print(i)
await asyncio.sleep(0.01) # 少しの遅延を発生させる
async def main():
print("Started!")
await asyncio.gather(display_evens(10), display_odds(10)) # gatherされたコルーチンは並行して実行される
print('Finished!')
# メインを実行する
asyncio.run(main())
出力:
Started!
0
1
2
3
4
5
6
7
8
9
Finished!
上記の出力を見ると、display_evens()
とdisplay_odds()
が同時に実行されていることがわかります。両方の関数からの出力が交互に表示され、すべての整数を印刷する単一の関数が実行されているかのように見えます。
関連記事
Pythonにおける非同期プログラミング