Python async defとは?awaitとの関係・使い方・async def main()の書き方をわかりやすく解説

async defは「非同期関数(コルーチン関数)」を定義する書き方で、awaitはその中で「待ち時間のある処理を待ち、待っている間はほかの処理に順番を譲る」ためのキーワードです。async defの関数は呼んだだけでは動かず、asyncio.run()かawaitで実行します。

この記事では、async def main()の基本形、awaitを並べても速くならない理由とgather()での同時実行、awaitの書き忘れで出るエラーや警告、FastAPIでasync defとdefのどちらを使うかまで、実行結果つきで解説します。実行結果はPython 3.12.15で確認しました(2026年10月18日確認)。

async defとawaitを一言でいうと

キーワード 役割 書ける場所
async def 非同期関数を定義する。呼ぶとコルーチンが返る どこでも
await コルーチンなどの結果を待つ。待つ間はほかの処理に譲る async defの中だけ
asyncio.run() いちばん外側で非同期処理を始める入口 通常の関数・トップレベル

非同期処理とは、通信やファイル読み込みのような「待ち時間」の間に、ほかの処理を進めるやり方です。1つのスレッドで、待ち時間を無駄にしないように処理を切り替えます。非同期処理そのものの考え方は「非同期処理とは?仕組みから実装まで初心者にもわかる完全解説」で詳しく解説しています。

async defで定義した関数は、呼んでも実行されない

import asyncio

async def hello():
    print("実行された")
    return 42

c = hello()
print(type(c).__name__)
c.close()
print(asyncio.run(hello()))
coroutine
実行された
42

hello()と呼んだ時点では「実行された」が表示されていません。async defの関数を呼ぶと、中身を実行する代わりにコルーチンというオブジェクトが返ります。実際に動かすには、asyncio.run()に渡すか、別のasync defの中でawaitします(上のc.close()は、使わなかったコルーチンの警告を出さないための後始末です)。

基本形:async def main() と asyncio.run(main())

非同期のプログラムは、ほぼ次の形で書きます。

import asyncio

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

asyncio.run(main())
main開始

処理の本体をasync def main()にまとめ、プログラムの最後でasyncio.run(main())を1回だけ呼びます。asyncio.run()が非同期処理を動かす仕組み(イベントループ)を用意し、main()が終わると片付けまで行います。asyncio.run()の引数や戻り値、よくあるエラーは「Python asyncio.run()の使い方|main()の書き方・戻り値・running event loopエラーの直し方」で解説しています。

asyncio.run()を付け忘れてmain()とだけ書くと、何も実行されずに警告が出ます。

RuntimeWarning: coroutine 'main' was never awaited

awaitを順番に並べても速くならない

import asyncio
import time

async def work(name, sec):
    await asyncio.sleep( sec)
    return f"{name}完了"

async def main():
    s = time.perf_counter()
    a = await work("A", 1)
    b = await work("B", 1)
    print(a, b, f"順番にawait: {time.perf_counter() - s:.1f}秒")

    s = time.perf_counter()
    a, b = await asyncio.gather(work("A", 1), work("B", 1))
    print(a, b, f"gatherで同時に: {time.perf_counter() - s:.1f}秒")

asyncio.run(main())
A完了 B完了 順番にawait: 2.0秒
A完了 B完了 gatherで同時に: 1.0秒

awaitには「結果が出るまで待つ」と「待っている間、ほかの処理に順番を譲る」の2つの意味があります。ところが1つずつawaitすると、Aが終わるまでBは始まらないので、合計2秒かかります。同時に進めたい処理はasyncio.gather()にまとめて渡すと、待ち時間が重なって1秒で終わります。gather()の詳しい使い方は「Python asyncio.gather()とは?複数の処理を同時に実行する方法」をご覧ください。

awaitを忘れたときのエラーと警告

値として使うとTypeError

import asyncio

async def get():
    return 10

async def main():
    v = get()          # await を忘れた
    print(v > 5)

asyncio.run(main())
TypeError: '>' not supported between instances of 'coroutine' and 'int'
sys:1: RuntimeWarning: coroutine 'get' was never awaited

変数vに入っているのは数値ではなくコルーチンです。「なぜか数値として使えない」「中身が<coroutine object ...>になっている」という症状は、ほぼawaitの書き忘れです。

戻り値を使わないと警告だけ

import asyncio

async def get():
    return 10

async def main():
    get()          # await を忘れた(戻り値も使っていない)
    print("終了")

asyncio.run(main())
RuntimeWarning: coroutine 'get' was never awaited
終了

エラーで止まらず、処理がまるごと実行されないまま進みます。coroutine '...' was never awaitedが出たら、その関数の前にawaitを付け忘れていると考えてください。

awaitはasync defの中でしか使えない

import asyncio

def g():
    await asyncio.sleep( 0)
SyntaxError: 'await' outside async function

普通のdefの中でawaitを書くと、実行前に文法エラーになります。awaitを使う関数はasync defにし、その関数を呼ぶ側もasync defにしてawaitする、という形で上へ伝わっていきます。いちばん上はasyncio.run()で始めます。

awaitできるのは、コルーチン、asyncio.create_task()で作ったTask、Futureなどの「待てるオブジェクト」だけです。普通の値をawaitするとTypeError: object int can't be used in 'await' expressionになります。

async with と async for

import asyncio

class Conn:
    async def __aenter__(self):
        print("open")
        return self
    async def __aexit__(self, *args):
        print("close")
        return False

async def agen():
    for i in range(3):
        await asyncio.sleep( 0.01)
        yield i

async def main():
    async with Conn() as c:
        print("処理中")
    print([x async for x in agen()])

asyncio.run(main())
open
処理中
close
[0, 1, 2]

async withは、接続を開く・閉じるといった前後処理にも待ち時間があるときに使います(__aenter__と__aexit__が非同期版の特殊メソッドです)。yieldを含むasync defは非同期ジェネレータになり、async forで1件ずつ取り出せます。

async defの中にtime.sleep()など同期処理を混ぜない

import asyncio
import time

async def blocking():
    time.sleep( 1)        # 同期のsleep
    return "完了"

async def nonblocking():
    await asyncio.to_thread(time.sleep, 1)
    return "完了"

async def main():
    s = time.perf_counter()
    await asyncio.gather(blocking(), blocking())
    print(f"time.sleepを混ぜた場合: {time.perf_counter() - s:.1f}秒")
    s = time.perf_counter()
    await asyncio.gather(nonblocking(), nonblocking())
    print(f"to_threadに逃がした場合: {time.perf_counter() - s:.1f}秒")

asyncio.run(main())
time.sleepを混ぜた場合: 2.0秒
to_threadに逃がした場合: 1.0秒

time.sleep()やrequests.get()のような同期処理は、待っている間も順番を譲らないので、gather()しても同時に進みません。async defの中では非同期に対応した関数を使い、どうしても同期の処理を呼ぶときはasyncio.to_thread()で別スレッドに逃がします。

やりたいこと async def の中で使うもの
一定時間待つ await asyncio.sleep()
HTTP通信 aiohttpやhttpxの非同期API
同期のライブラリしかない処理 await asyncio.to_thread(関数, 引数)
複数の処理を同時に進める asyncio.gather()、asyncio.create_task()

to_thread()については「Python asyncio.to_thread()とは?同期処理を非同期で動かす方法」で解説しています。

FastAPIではasync defとdefのどちらを使う?

「async def」で調べる人の多くが出会うのが、WebフレームワークFastAPIの公式ドキュメントにある使い分けです(2026年10月18日確認)。要点は次のとおりです。

  • 使っているライブラリがawaitで呼ぶよう案内している(非同期対応)なら、処理の関数をasync defで書く
  • データベースなど外部とやり取りするライブラリがawaitに対応していないなら、普通のdefで書く。FastAPIはdefの関数を別のスレッドプールで実行するので、サーバー全体は止まらない
  • 外部とのやり取りがなく待ち時間がない処理なら、async defでよい

上の「同期処理を混ぜない」と同じ理由で、async defの中で非対応のライブラリを呼ぶのがいちばん危険です。迷ったら、中でawaitするものがあるかどうかで決めると間違えにくくなります。

よくある質問

Q. async defとdefの違いは?

defの関数は呼ぶとすぐ実行されて値を返します。async defの関数は呼ぶとコルーチンが返るだけで、awaitするかasyncio.run()に渡したときに実行されます。またawaitはasync defの中でしか書けません。

Q. async defの関数を普通の関数から呼ぶには?

プログラムのいちばん外側でasyncio.run(関数())と呼びます。すでに非同期処理が動いている中(async defの中)ではasyncio.run()ではなくawaitを使います。

Q. coroutine was never awaitedという警告の意味は?

async defの関数を呼んだのにawaitしていないため、中身が一度も実行されなかったという意味です。呼び出しの前にawaitを付けるか、asyncio.create_task()やasyncio.gather()に渡してください。

Q. awaitを付けたのに処理が速くならないのはなぜ?

1つずつawaitすると前の処理が終わるまで次が始まらないためです。同時に進めたい処理はasyncio.gather()にまとめます。またtime.sleep()のような同期処理が混ざっていると、gather()しても同時には進みません。

Q. FastAPIではasync defとdefのどちらを使えばいい?

FastAPIの公式ドキュメントでは、awaitで呼ぶ非同期対応のライブラリを使うならasync def、awaitに対応していないライブラリを使うなら普通のdefを勧めています。defの関数はFastAPIが別スレッドで実行します。

async defとawaitのまとめ

  • async defの関数は呼んでも実行されず、コルーチンが返る
  • 基本形はasync def main()と、最後に1回だけasyncio.run(main())
  • awaitは「待つ」と「待つ間ほかに譲る」。順番に並べても速くならないので、同時に進めるならgather()
  • coroutine ... was never awaitedはawaitの書き忘れ
  • awaitはasync defの中だけ。外で書くとSyntaxError
  • time.sleep()など同期処理を混ぜない。必要ならasyncio.to_thread()

非同期処理は、読んだだけでは「どこで待っているのか」がつかみにくい分野です。テックジムでは、自分のペースで課題を進めながら、つまずいたところをその場でトレーナーに質問できます。入会金33,000円+月額22,000円で通い放題(学生は月額16,500円)です。まずは無料講座で雰囲気を確かめてみてください。

あわせて読みたい

対面・無料|東京・永田町で毎週土曜日13時

1回の参加で、プログラミングの構造がわかる

ゼロからはじめるPython入門講座

8年間・累計600回開催/これまで全国で2万人以上が参加

「やはり対面で聞けるのはとても気づきがあり、価値があると思います」

参加者の声

  • 本当に初心者からの講座です。プログラミングに触ったことがなくて大丈夫です
  • 対面だから、その場で聞けます。つまずいた所を持ち帰らずに解決できます
  • 講座は1時間。そのあとも残って学習を続けられ、講師にも質問できます
  • 独学で挫折した方も。糸口が見つかって、講座のあと会員になる方が多くいます

無料で参加する|開催日程を見る

参加費は無料です