1
2

Pythonのasyncawaitキーワードは非同期タスクを管理するために使用されます。

非同期プログラミングは、他のタスクをブロックすることなく複数のタスクを同時に処理できるようにします。Pythonでは、コルーチンを使用してこれを実現します。

asyncキーワードは、関数を非同期コルーチンとして定義するために使用され、awaitキーワードはコルーチン関数内で別のコルーチンの実行を待つために使用されます。

コルーチンとは何か?

コルーチンは、特定のポイントで実行を一時停止し、再開する機能を持つ通常の関数とほぼ同じです。これにより、非ブロッキング動作が可能になり、コルーチンの実行が遅延した場合や、他のコードを実行するために意図的に実行を一時停止することができます。

コルーチンはジェネレータ関数と非常に密接に関連しています。実際、Python 3.5でasyncawaitが導入される以前は、ジェネレータ関数を特別な方法で使用してコルーチンを実装していました。

Python 3.5以降のバージョンでは、asyncawaitという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における非同期プログラミング

asyncioモジュール

1
2
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
1
2