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などでイベントループに制御を返したときに動き始めるからです。



