python async と await を使って非同期処理

Python

非同期処理とは、処理が終わるのを待たずに次の処理を進める仕組みのことで、特にDiscord Botのように外部APIと通信する際に重要になります


サンプルコード

async と await を使った処理の順番がわかるシンプルなサンプルコードを紹介します
このコードでは、async / await を使って処理の順番を確認できるようにします

import asyncio

async def task1():
    print("Task 1: 開始")
    await asyncio.sleep(2)  # 2秒待機(非同期)
    print("Task 1: 終了")

async def task2():
    print("Task 2: 開始")
    await asyncio.sleep(1)  # 1秒待機(非同期)
    print("Task 2: 終了")

async def main():
    print("Main: 開始")

    # それぞれのタスクを同時に実行
    await asyncio.gather(task1(), task2())

    print("Main: 終了")

# 非同期処理の実行
asyncio.run(main())

実行結果(出力順)

Main: 開始
Task 1: 開始
Task 2: 開始
Task 2: 終了
Task 1: 終了
Main: 終了

処理の流れ

  1. main() が呼ばれる
    • print(“Main: 開始”) が実行される
  2. asyncio.gather(task1(), task2()) により task1() と task2() が 同時に実行 される
    • task1() の “Task 1: 開始” が出力される
    • task2() の “Task 2: 開始” が出力される
  3. task2() は await asyncio.sleep(1) で 1秒待機
  4. task1() は await asyncio.sleep(2) で 2秒待機
  5. 1秒経過後、task2() が再開し “Task 2: 終了” を出力
  6. さらに1秒経過後(合計2秒経過)、task1() が再開し “Task 1: 終了” を出力
  7. asyncio.gather() の処理がすべて終わり、main() の “Main: 終了” が出力される

ポイント

  • await asyncio.sleep(n) は 処理を中断してn秒待つ(その間、他のタスクが動ける)。
  • asyncio.gather(task1(), task2()) で 複数のタスクを並行実行 できる。
  • task2() は 1秒で終わるので、task1() より先に “Task 2: 終了” が出る。

まとめ

async は 「asynchronous(非同期)」 の略です。

Python では、async def を使うことで「非同期関数(コルーチン)」を定義できます
非同期(asynchronous)とは、「処理が終わるのを待たずに次の処理を進める」仕組みのことです

  • await は 非同期処理の完了を待つ キーワード(async なしでは await は使えない)

つまり

  • async = 「非同期の」
  • await = 「非同期処理の完了を待つ」

という意味になります


Discord botでの活用例

以前の記事で紹介した株価ローソク足チャートを返すDiscord botの関数でasyncを使用しているので解説します

以下にDiscord botのコードの一部を書きます
コード全体は以前の記事を確認してください

# スラッシュコマンドの登録
@tree.command(name="ticker", description="指定した銘柄の株価チャートを取得")
async def ticker(interaction: discord.Interaction, symbol: str):
    await interaction.response.defer()  # 処理中の表示
    buf = create_candlestick_chart(symbol)

    if buf:
        await interaction.followup.send(file=discord.File(buf, filename=f"{symbol}_chart.png"))
    else:
        await interaction.followup.send("⚠️ チャートの生成に失敗しました。ティッカーを確認してください。")

# Botが起動したときにスラッシュコマンドを同期
@bot.event
async def on_ready():
    await tree.sync()
    print(f"Logged in as {bot.user}")

1. async def で非同期関数を定義

async def ticker(interaction: discord.Interaction, symbol: str):

この関数は 非同期関数 になっています
非同期関数 (async def) は、通常の関数 (def) と異なり、処理を中断 (await) しながら進めることができます

2. await で非同期処理の完了を待つ

await interaction.response.defer()  # 処理中の表示

これは、Discordのスラッシュコマンドのレスポンスを「処理中」の状態にするための非同期処理です
await を使うことで、この処理が完了するまで待つことができます
(もし await を付けずに interaction.response.defer() を実行すると、関数の実行が終わる前に次の処理に進んでしまい、エラーの原因になります)

await interaction.followup.send(file=discord.File(buf, filename=f"{symbol}_chart.png"))

これも同じく await を使って、Discordへの画像送信が完了するまで待つようにしています

3. on_ready() の await

@bot.event
async def on_ready():
    await tree.sync()
    print(f"Logged in as {bot.user}")

この await tree.sync() も非同期処理で、Botが起動したときにスラッシュコマンドを同期するためのものです
これが完了するのを待たずに print() を実行すると、コマンドの登録が終わる前にBotが動き出してしまうため、適切に await を使って処理の順番を守っています


まとめ

  • async def で関数を非同期化することで、await を使った非同期処理が可能になる
  • await は、時間のかかる処理(Discord APIへのリクエストなど)が終わるまで待機するために使う
  • await を適切に使わないと、処理の順番が崩れてエラーが発生しやすくなる

このようにDiscordのAPI通信 (interaction.response.defer() や interaction.followup.send()) など、外部サービスとのやり取りが含まれているため、async / await を適切に使うことが重要になっています

コメント