Python asyncio.create_task()の使い方|awaitとの違い・戻り値・gather/TaskGroup・キャンセルと例外

asyncio.create_task()は、コルーチンをTask(タスク)に包んで実行を予約し、そのTaskを返す関数です。awaitのようにその場で終わるまで待たないので、複数の処理を同時に進められ、結果はあとでawait タスクと書いて受け取ります。Taskは変数に入れて保持し、最後に必ずawaitするのが基本の使い方です。

この記事では、公式ドキュメント(Coroutines and Tasks、Python 3.15.0版、2026年10月18日確認)の説明に沿って、create_task()の書き方、await・gather()・TaskGroupとの違い、キャンセル・例外・よくあるエラーを、Python 3.12で実行した結果つきで解説します。非同期処理そのものが初めての方はasync defとawaitの解説から読むと理解しやすくなります。

asyncio.create_task()の基本の使い方

書き方はasyncio.create_task(コルーチン)です。関数名に()を付けて呼び出した「コルーチン」を渡します。引数があるときは、そのままwork("A", 0.3)のように渡します。

import asyncio
import time

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

async def main_await():
    s = time.perf_counter()
    a = await work("A", 0.3)
    b = await work("B", 0.3)
    print(f"await だけ: {time.perf_counter() - s:.1f}秒", a, b)

async def main_task():
    s = time.perf_counter()
    ta = asyncio.create_task(work("A", 0.3))
    tb = asyncio.create_task(work("B", 0.3))
    print("タスク作成直後(まだ動いていない)")
    a = await ta
    b = await tb
    print(f"create_task: {time.perf_counter() - s:.1f}秒", a, b)

asyncio.run(main_await())
asyncio.run(main_task())

実行結果:

A 完了
B 完了
await だけ: 0.6秒 A B
タスク作成直後(まだ動いていない)
A 完了
B 完了
create_task: 0.3秒 A B

awaitを2回並べると0.3秒+0.3秒で0.6秒かかりますが、先に2つのTaskを作っておくと待ち時間が重なり、0.3秒で終わります。

注意したいのは、create_task()は実行を予約するだけだという点です。作った直後はまだ動いておらず、今のコルーチンがawaitなどでイベントループに制御を返したときに動き始めます。上の結果で「タスク作成直後」が先に表示されているのはこのためです。

戻り値はTaskオブジェクト

create_task()の戻り値は処理の結果ではなくTaskオブジェクトです。結果はawaitするか、完了後にresult()で取り出します。

メソッド わかること・できること
await task 完了を待って結果を受け取る(例外もここで出る)
task.done() 終わったかどうか(True/False)
task.result() 結果を取り出す(未完了だとInvalidStateError)
task.cancel() キャンセルを依頼する
task.cancelled() キャンセルされたかどうか
task.get_name() name=で付けた名前
task.add_done_callback(fn) 完了時に呼ぶ関数を登録
import asyncio

async def work(name, sec):
    await asyncio.sleep( sec)
    return name

async def main():
    t = asyncio.create_task(work("状態", 0.2), name="job-1")
    print(t.get_name(), "done():", t.done())
    await t
    print("done():", t.done(), "result():", t.result())

    t4 = asyncio.create_task(work("x", 0.1))
    try:
        t4.result()
    except asyncio.InvalidStateError as e:
        print("InvalidStateError:", e)
    await t4

asyncio.run(main())

実行結果:

job-1 done(): False
done(): True result(): 状態
InvalidStateError: Result is not set.

await・gather・TaskGroupとの違い

書き方 動き 向いている場面
await work() その行で終わるまで待つ 1つずつ順番に実行したい
asyncio.create_task(work()) 実行を予約してTaskを返す 走らせながら別の処理をしたい、途中でキャンセルや状態確認をしたい
asyncio.gather(a(), b()) まとめて実行し、全部の結果をリストで返す 決まった数の処理を同時に実行して全部待つ
asyncio.TaskGroup(3.11以降) async withを抜けるときに全部を待ち、1つが失敗したら残りをキャンセル 関連するタスクを安全にまとめて扱う

公式ドキュメントも、create_task()の新しい代わりとしてTaskGroup.create_task()を紹介しています。Python 3.11以降なら、まずTaskGroupを検討するとよいでしょう。

import asyncio

async def work(name, sec):
    await asyncio.sleep( sec)
    return name

async def main():
    async with asyncio.TaskGroup() as tg:
        t1 = tg.create_task(work("A", 0.2))
        t2 = tg.create_task(work("B", 0.1))
    print("TaskGroup:", t1.result(), t2.result())

asyncio.run(main())

実行結果:

TaskGroup: A B

gather()の結果の順番やreturn_exceptionsについてはasyncio.gatherの使い方、終わった順に受け取る方法はasyncio.as_completed()の使い方で詳しく扱っています。

キャンセルとタイムアウト

task.cancel()を呼ぶと、そのタスクの中で次にawaitしたときにCancelledErrorが起きて止まります。awaitを含まない重い計算ループは、途中でキャンセルできません。

import asyncio

async def work(name, sec):
    await asyncio.sleep( sec)
    return name

async def main():
    t = asyncio.create_task(work("cancel対象", 1))
    await asyncio.sleep( 0.05)
    t.cancel()
    try:
        await t
    except asyncio.CancelledError:
        print("CancelledError で止まった / cancelled():", t.cancelled())

    t2 = asyncio.create_task(asyncio.sleep( 1, result="遅い"))
    try:
        await asyncio.wait_for(t2, timeout=0.2)
    except TimeoutError:
        print("TimeoutError / cancelled():", t2.cancelled())

asyncio.run(main())

実行結果:

CancelledError で止まった / cancelled(): True
TimeoutError / cancelled(): True

wait_for()で時間切れになると、待っていたタスクはキャンセルされます。詳しくはasyncio.wait_for()の使い方をご覧ください。

例外はawaitするまで表に出ない

タスクの中で例外が起きても、その場ではプログラムは止まりません。例外はTaskの中に保存され、awaitやresult()で取り出したときに発生します。

import asyncio

async def boom():
    raise ValueError("タスク内で失敗")

async def main():
    t = asyncio.create_task(boom())
    await asyncio.sleep( 0.1)
    print("ここまで到達(例外はまだ出ない) done():", t.done())
    try:
        await t
    except ValueError as e:
        print("await で初めて:", e)

asyncio.run(main())

実行結果:

ここまで到達(例外はまだ出ない) done(): True
await で初めて: タスク内で失敗

awaitしないまま放置すると、失敗に気づけません。公式ドキュメントによると、例外が取り出されないままTaskが回収されると「Task exception was never retrieved」というログが出ます。TaskGroupなら、失敗した例外がExceptionGroupとしてまとめて投げられるので見落としません。

import asyncio

async def work(name, sec):
    await asyncio.sleep( sec)
    return name

async def boom():
    raise ValueError("タスク内で失敗")

async def main():
    try:
        async with asyncio.TaskGroup() as tg:
            tg.create_task(work("A", 0.2))
            tg.create_task(boom())
    except* ValueError as eg:
        print("ExceptionGroup:", [str(e) for e in eg.exceptions])

asyncio.run(main())

実行結果:

ExceptionGroup: ['タスク内で失敗']

awaitしないで放っておくとき(fire-and-forget)の注意

公式ドキュメントは「タスクが実行途中で消えないよう、この関数の結果への参照を保存すること」と注意しています。イベントループはタスクを弱い参照でしか持たないため、どこからも参照されていないタスクは、終わる前でもガベージコレクションで回収される可能性があるからです。

結果を待たずに裏で動かしたい場合は、公式ドキュメントの例のように集合に入れて保持し、終わったら取り除きます。

import asyncio

background_tasks = set()

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

async def main():
    for i in range(3):
        task = asyncio.create_task(work(f"bg{i}", 0.1))
        background_tasks.add(task)
        task.add_done_callback(background_tasks.discard)
    await asyncio.sleep( 0.3)
    print("残り:", len(background_tasks))

asyncio.run(main())

実行結果:

bg0 完了
bg1 完了
bg2 完了
残り: 0

ただし、この形はタスクをawaitしないので、失敗しても例外は取り出されません。失敗を確実に拾いたいならTaskGroupを使います。

よくあるエラーと原因

エラー 原因 直し方
RuntimeError: no running event loop async defの外(イベントループが動いていない場所)で呼んだ async def main()の中で呼び、asyncio.run(main())で実行する
TypeError: a coroutine was expected, got <function work at 0x…> create_task(work)と()を付け忘れた create_task(work(引数))と呼び出して渡す
asyncio.InvalidStateError: Result is not set. 完了前にresult()を呼んだ await taskで待つか、done()を確認する
普通の関数で全体が止まる time.sleep()やrequestsなどのブロッキング処理をタスク内で実行した asyncio.to_thread()で別スレッドに逃がす

上の2つのエラーは、実際に次のように出ます(アドレスの数字は実行ごとに変わります)。

RuntimeError: no running event loop
TypeError: a coroutine was expected, got <function work at 0x108571580>

create_taskとto_threadの違い

create_task()が同時に進められるのは、awaitで待ちを譲るコルーチンだけです。time.sleep()のような普通の関数はasyncio.to_thread()で別スレッドに渡すと、ほかのタスクを止めずに済みます。

import asyncio
import time

def blocking(sec):
    time.sleep(sec)  # 普通の(同期)関数
    return "blocking 完了"

async def tick():
    for i in range(3):
        print("tick", i)
        await asyncio.sleep( 0.1)

async def main():
    s = time.perf_counter()
    t = asyncio.create_task(asyncio.to_thread(blocking, 0.3))
    await tick()
    print(await t, f"{time.perf_counter() - s:.1f}秒")

asyncio.run(main())

実行結果:

tick 0
tick 1
tick 2
blocking 完了 0.3秒

バージョンによる違い

バージョン 変更点(公式ドキュメントより)
3.7 asyncio.create_task()が追加された
3.8 name引数が追加された
3.11 context引数が追加された。TaskGroupも3.11で追加
3.14 eager_start引数が追加された(create_task()の呼び出し中にすぐ実行を始めるかを指定)

3.6以前ではasyncio.ensure_future()を使っていました(asyncio関数一覧参照)。

よくある質問

Q. create_task()したタスクはawaitしないといけませんか?

必須ではありませんが、awaitしないと結果も例外も受け取れません。放っておく場合でも、Taskへの参照を集合などに保持しないと、途中で回収されることがあります。

Q. create_task()とgather()はどちらを使えばいいですか?

決まった数の処理を同時に動かして全部待つだけならgather()が簡単です。走らせながら別の処理をしたい、途中でキャンセルしたいときはcreate_task()を使います。

Q. create_task()の戻り値は何ですか?

Taskオブジェクトです。処理の結果は、await taskと書くか、完了後にtask.result()で取り出します。

Q. create_task()で処理がすぐ始まらないのはなぜですか?

create_task()は実行を予約するだけで、今のコルーチンがawaitなどでイベントループに制御を返したときに動き始めるからです。

あわせて読みたい

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

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

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

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

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

参加者の声

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

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

参加費は無料です