コルーチンと Task

この節では、コルーチンと Task を利用する高レベルの asyncio の API の概略を解説します。

コルーチン

async/await 構文で宣言されたコルーチンは、 asyncio を使ったアプリケーションを書く手法として好ましいです。 例えば、次のコードスニペット (Python 3.7 以降が必要) は "hello" を出力し、そこから 1 秒待って "world" を出力します:

>>> import asyncio

>>> async def main():
...     print('hello')
...     await asyncio.sleep(1)
...     print('world')

>>> asyncio.run(main())
hello
world

単にコルーチンを呼び出しただけでは、コルーチンの実行スケジュールは予約されていないことに注意してください:

>>> main()
<coroutine object main at 0x1053bb7c8>

実際にコルーチンを走らせるために、 asyncio は3つの機構を提供しています:

  • 最上位のエントリーポイントである "main()" 関数を実行する asyncio.run() 関数 (上の例を参照してください。)

  • コルーチンを await すること。次のコード片は 1 秒間待機した後に "hello" と出力し、 更に 2 秒間待機してから "world" と出力します:

    import asyncio
    import time
    
    async def say_after(delay, what):
        await asyncio.sleep(delay)
        print(what)
    
    async def main():
        print(f"started at {time.strftime('%X')}")
    
        await say_after(1, 'hello')
        await say_after(2, 'world')
    
        print(f"finished at {time.strftime('%X')}")
    
    asyncio.run(main())
    

    予想される出力:

    started at 17:13:52
    hello
    world
    finished at 17:13:55
    
  • asyncio の Tasks としてコルーチンを並行して走らせる asyncio.create_task() 関数。

    上のコード例を編集して、ふたつの say_after コルーチンを 並行して 走らせてみましょう:

    async def main():
        task1 = asyncio.create_task(
            say_after(1, 'hello'))
    
        task2 = asyncio.create_task(
            say_after(2, 'world'))
    
        print(f"started at {time.strftime('%X')}")
    
        # Wait until both tasks are completed (should take
        # around 2 seconds.)
        await task1
        await task2
    
        print(f"finished at {time.strftime('%X')}")
    

    予想される出力が、スニペットの実行が前回よりも 1 秒早いことを示していることに注意してください:

    started at 17:14:32
    hello
    world
    finished at 17:14:34
    

Awaitable

あるオブジェクトを await 式の中で使うことができる場合、そのオブジェクトを awaitable オブジェクトと言います。多くの asyncio API は awaitable を受け取るように設計されています。

awaitable オブジェクトには主に3つの種類があります: コルーチン, Task, そして Future です

コルーチン

Python のコルーチンは awaitable であり、そのため他のコルーチンを待機させられます:

import asyncio

async def nested():
    return 42

async def main():
    # Nothing happens if we just call "nested()".
    # A coroutine object is created but not awaited,
    # so it *won't run at all*.
    nested()

    # Let's do it differently now and await it:
    print(await nested())  # will print "42".

asyncio.run(main())

重要

このドキュメントにおいて「コルーチン」という用語は以下2つの密接に関連した概念に対して使用できます:

  • コルーチン関数: async def 関数;

  • コルーチンオブジェクト: コルーチン関数 を呼び出すと返ってくるオブジェクト.

asyncio は、古くからある ジェネレータベース のコルーチンもサポートしています。

Task

Task は、コルーチンを 並行に スケジュールするのに使います。

asyncio.create_task() のような関数で、コルーチンが Task にラップされているとき、自動的にコルーチンは即時実行されるようにスケジュールされます:

import asyncio

async def nested():
    return 42

async def main():
    # Schedule nested() to run soon concurrently
    # with "main()".
    task = asyncio.create_task(nested())

    # "task" can now be used to cancel "nested()", or
    # can simply be awaited to wait until it is complete:
    await task

asyncio.run(main())

Future

Future は、非同期処理の 最終結果 を表現する特別な 低レベルの awaitable オブジェクトです。

Future オブジェクトが他の awaitable を 待機させている と言うときは、ある場所で Future が解決されるまでコルーチンが待機するということです。

asyncio での Future オブジェクトは、async/await と共に使用できるようにするため、コールバック形式のコードを使用できるように設計すべきです。

通常、アプリケーション水準のコードで Future オブジェクトを作る 必要はありません

Future オブジェクトはライブラリや asyncio の API で表に出ることもあり、他の awaitable を待機させられます:

async def main():
    await function_that_returns_a_future_object()

    # this is also valid:
    await asyncio.gather(
        function_that_returns_a_future_object(),
        some_python_coroutine()
    )

Future オブジェクトを返す低レベル関数の良い例は loop.run_in_executor() です。

非同期プログラムの実行

asyncio.run(coro, *, debug=False)

coroutine coro を実行し、結果を返します。

この関数は、非同期イベントループの管理と 非同期ジェネレータの終了処理 を行いながら、渡されたコルーチンを実行します。

この関数は、同じスレッドで他の非同期イベントループが実行中のときは呼び出せません。

debugTrue の場合、イベントループはデバッグモードで実行されます。

この関数は常に新しいイベントループを作成し、終了したらそのイベントループを閉じます。 この関数は非同期プログラムのメインのエントリーポイントとして使われるべきで、理想的には 1 回だけ呼び出されるべきです。

以下はプログラム例です:

async def main():
    await asyncio.sleep(1)
    print('hello')

asyncio.run(main())

バージョン 3.7 で追加: 重要: この関数は Python 3.7 で 暫定 API として asyncio に追加されました。

Task の作成

asyncio.create_task(coro)

coro coroutineTask でラップし、その実行をスケジュールします。 Task オブジェクトを返します。

その Task オブジェクトは get_running_loop() から返されたループの中で実行されます。現在のスレッドに実行中のループが無い場合は、 RuntimeError が送出されます。

この関数は Python 3.7 で追加 されました。 Python 3.7 より前では、代わりに低レベルの asyncio.ensure_future() 関数が使えます:

async def coro():
    ...

# In Python 3.7+
task = asyncio.create_task(coro())
...

# This works in all Python versions but is less readable
task = asyncio.ensure_future(coro())
...

バージョン 3.7 で追加.

スリープ

coroutine asyncio.sleep(delay, result=None, *, loop=None)

delay 秒だけ停止します。

result が提供されている場合は、コルーチン完了時にそれが呼び出し元に返されます。

sleep() は常に現在の Task を一時中断し、他の Task が実行されるのを許可します。

loop 引数は非推奨で、 Python 3.10 で削除される予定です。

現在の時刻を5秒間、毎秒表示するコルーチンの例:

import asyncio
import datetime

async def display_date():
    loop = asyncio.get_running_loop()
    end_time = loop.time() + 5.0
    while True:
        print(datetime.datetime.now())
        if (loop.time() + 1.0) >= end_time:
            break
        await asyncio.sleep(1)

asyncio.run(display_date())

並行な Task 実行

awaitable asyncio.gather(*aws, loop=None, return_exceptions=False)

aws シーケンスにある awaitable オプジェクト並行 実行します。

aws にある awaitable がコルーチンである場合、自動的に Task としてスケジュールされます。

全ての awaitable が正常終了した場合、その結果は返り値を集めたリストになります。 返り値の順序は、 aws での awaitable の順序に相当します。

return_exceptionsFalse である場合(デフォルト)、gather() で await しているタスクに対して、最初の例外が直接伝えられます。aws に並んでいる他の awaitable は、キャンセルされずに 引き続いて実行されます。

return_exceptionsTrue だった場合、例外は成功した結果と同じように取り扱われ、結果リストに集められます。

gather()キャンセル された場合、起動された全ての (未完了の) awaitable も キャンセル されます。

aws シーケンスにある Task あるいは Future が キャンセル された場合、 CancelledError を送出したかのうように扱われます。つまり、この場合 gather() 呼び出しはキャンセル されません。 これは、起動された 1 つの Task あるいは Future のキャンセルが、他の Task あるいは Future のキャンセルを引き起こすのを避けるためです。

以下はプログラム例です:

import asyncio

async def factorial(name, number):
    f = 1
    for i in range(2, number + 1):
        print(f"Task {name}: Compute factorial({i})...")
        await asyncio.sleep(1)
        f *= i
    print(f"Task {name}: factorial({number}) = {f}")

async def main():
    # Schedule three calls *concurrently*:
    await asyncio.gather(
        factorial("A", 2),
        factorial("B", 3),
        factorial("C", 4),
    )

asyncio.run(main())

# Expected output:
#
#     Task A: Compute factorial(2)...
#     Task B: Compute factorial(2)...
#     Task C: Compute factorial(2)...
#     Task A: factorial(2) = 2
#     Task B: Compute factorial(3)...
#     Task C: Compute factorial(3)...
#     Task B: factorial(3) = 6
#     Task C: Compute factorial(4)...
#     Task C: factorial(4) = 24

バージョン 3.7 で変更: gather 自身がキャンセルされた場合は、 return_exceptions の値に関わらずキャンセルが伝搬されます。

キャンセルからの保護

awaitable asyncio.shield(aw, *, loop=None)

キャンセル から awaitable オプジェクト を保護します。

aw がコルーチンだった場合、自動的に Task としてスケジュールされます。

文:

res = await shield(something())

は、以下と同じです

res = await something()

それを含むコルーチンがキャンセルされた場合を 除きsomething() 内で動作している Task はキャンセルされません。 something() 側から見るとキャンセルは発生しません。 呼び出し元がキャンセルされた場合でも、 "await" 式は CancelledError を送出します。

注意: something() が他の理由 (例えば、原因が自分自身) でキャンセルされた場合は shield() でも保護できません。

完全にキャンセルを無視したい場合 (推奨はしません) は、 shield() 関数は次のように try/except 節と組み合わせることになるでしょう:

try:
    res = await shield(something())
except CancelledError:
    res = None

タイムアウト

coroutine asyncio.wait_for(aw, timeout, *, loop=None)

aw awaitable が、完了するかタイムアウトになるのを待ちます。

aw がコルーチンだった場合、自動的に Task としてスケジュールされます。

timeout には None もしくは待つ秒数の浮動小数点数か整数を指定できます。 timeoutNone の場合、 Future が完了するまで待ちます。

タイムアウトが起きた場合は、 Task をキャンセルし asyncio.TimeoutError を送出します。

Task の キャンセル を避けるためには、 shield() の中にラップしてください。

この関数は、 Future が実際にキャンセルされるまで待つので、全体の待ち時間は timeout を超えることもあります。

待機が中止された場合 aw も中止されます。

loop 引数は非推奨で、 Python 3.10 で削除される予定です。

以下はプログラム例です:

async def eternity():
    # Sleep for one hour
    await asyncio.sleep(3600)
    print('yay!')

async def main():
    # Wait for at most 1 second
    try:
        await asyncio.wait_for(eternity(), timeout=1.0)
    except asyncio.TimeoutError:
        print('timeout!')

asyncio.run(main())

# Expected output:
#
#     timeout!

バージョン 3.7 で変更: aw がタイムアウトでキャンセルされたとき、 wait_foraw がキャンセルされるまで待ちます。 以前は、すぐに asyncio.TimeoutError を送出していました。

要素の待機

coroutine asyncio.wait(aws, *, loop=None, timeout=None, return_when=ALL_COMPLETED)

aws 集合にある awaitable オブジェクト並行 実行し、 return_when で指定した条件が満たされるまで待ちます。

aws にある awaitable のどれかがコルーチンの場合、自動的に Task としてスケジュールされます。 コルーチンオブジェクトを wait() に直接渡すのは 紛らわしい振る舞い を引き起こすため非推奨です。

Task と Future の 2 つ (done, pending) を返します。

使い方:

done, pending = await asyncio.wait(aws)

loop 引数は非推奨で、 Python 3.10 で削除される予定です。

timeout (浮動小数点数または整数) が指定されていたら、処理を返すのを待つ最大秒数を制御するのに使われます。

この関数は asyncio.TimeoutError を送出しないことに注意してください。 タイムアウトが起きたときに完了していなかった Future や Task は、2 つ目の集合の要素として返されるだけです。

return_when でこの関数がいつ結果を返すか指定します。指定できる値は以下の 定数のどれか一つです:

定数

説明

FIRST_COMPLETED

いずれかの Future が終了したかキャンセルされたときに返します。

FIRST_EXCEPTION

いずれかの Future が例外の送出で終了した場合に返します。 例外を送出したフューチャがない場合は、ALL_COMPLETED と等価になります。

ALL_COMPLETED

すべての Future が終了したかキャンセルされたときに返します。

wait_for() と異なり、 wait() はタイムアウトが起きたときに Future をキャンセルしません。

注釈

wait() は自動的にコルーチンを Task としてスケジュールし、その後、暗黙的に作成された Task オブジェクトを組になった集合 (done, pending) に入れて返します。 従って、次のコードは予想した通りには動作しません:

async def foo():
    return 42

coro = foo()
done, pending = await asyncio.wait({coro})

if coro in done:
    # This branch will never be run!

上のスクリプト片は次のように修正できます:

async def foo():
    return 42

task = asyncio.create_task(foo())
done, pending = await asyncio.wait({task})

if task in done:
    # Everything will work as expected now.

wait() にコルーチンオブジェクトを直接渡すのは非推奨です。

asyncio.as_completed(aws, *, loop=None, timeout=None)

Run awaitable objects in the aws set concurrently. Return an iterator of Future objects. Each Future object returned represents the earliest result from the set of the remaining awaitables.

全フューチャが終了する前にタイムアウトが発生した場合 asyncio.TimeoutError を送出します。

以下はプログラム例です:

for f in as_completed(aws):
    earliest_result = await f
    # ...

外部スレッドからのスケジュール

asyncio.run_coroutine_threadsafe(coro, loop)

与えられたイベントループにコルーチンを送ります。 この処理は、スレッドセーフです。

他の OS スレッドから結果を待つための concurrent.futures.Future を返します。

この関数は、イベントループが動作しているスレッドとは異なる OS スレッドから呼び出すためのものです。 例えば次のように使います:

# Create a coroutine
coro = asyncio.sleep(1, result=3)

# Submit the coroutine to a given loop
future = asyncio.run_coroutine_threadsafe(coro, loop)

# Wait for the result with an optional timeout argument
assert future.result(timeout) == 3

コルーチンから例外が送出された場合、返された Future に通知されます。 これはイベントループの Task をキャンセルするのにも使えます:

try:
    result = future.result(timeout)
except asyncio.TimeoutError:
    print('The coroutine took too long, cancelling the task...')
    future.cancel()
except Exception as exc:
    print(f'The coroutine raised an exception: {exc!r}')
else:
    print(f'The coroutine returned: {result!r}')

このドキュメントの asyncio-multithreading 節を参照してください。

他の asyncio 関数とは異なり、この関数は明示的に渡される loop 引数を必要とします。

バージョン 3.5.1 で追加.

イントロスペクション

asyncio.current_task(loop=None)

現在実行中の Task インスタンスを返します。実行中の Task が無い場合は None を返します。

loopNone の場合、 get_running_loop() が現在のループを取得するのに使われます。

バージョン 3.7 で追加.

asyncio.all_tasks(loop=None)

ループで実行された Task オブジェクトでまだ完了していないものの集合を返します。

loopNone の場合、 get_running_loop() は現在のループを取得するのに使われます。

バージョン 3.7 で追加.

Task オブジェクト

class asyncio.Task(coro, *, loop=None)

Python コルーチン を実行する Future オブジェクトです。 スレッドセーフではありません。

Task はイベントループのコルーチンを実行するのに使われます。 Future でコルーチンが待機している場合、 Task は自身のコルーチンの実行を一時停止させ、 Future の完了を待ちます。 Future が 完了 したら、 Task が内包しているコルーチンの実行を再開します。

イベントループは協調スケジューリングを使用します。つまり、イベントループは同時に 1 つの Task のみ実行します。 Task が Future の完了を待っているときは、イベントループは他の Task やコールバックを動作させるか、 IO 処理を実行します。

Task を作成するには高レベルの asyncio.create_task() 関数、あるいは低レベルの loop.create_task() 関数や ensure_future() 関数を使用してください。 手作業での Task の実装は推奨されません。

実行中のタスクをキャンセルするためには、cancel() メソッドを使用します。このメソッドを呼ぶと、タスクはそれを内包するコルーチンに対して CancelledError 例外を送出します。キャンセルの際にコルーチンが Future オブジェクトを待っていた場合、その Future オブジェクトはキャンセルされます。

cancelled() は、タスクがキャンセルされたかを調べるのに使用できます。タスクを内包するコルーチンで CancelledError 例外が抑制されておらず、かつタスクが実際にキャンセルされている場合に、このメソッドは True を変えます。

asyncio.Task は、Future.set_result()Future.set_exception() を除いて、Future の API をすべて継承しています。

Task は contextvars モジュールをサポートします。Task が作られたときに現在のコンテキストがコピーされ、のちに Task のコルーチンを実行する際に、コピーされたコンテキストが使用されます。

バージョン 3.7 で変更: contextvars モジュールのサポートを追加。

cancel()

このタスクに、自身のキャンセルを要求します。

This arranges for a CancelledError exception to be thrown into the wrapped coroutine on the next cycle of the event loop.

The coroutine then has a chance to clean up or even deny the request by suppressing the exception with a try ... ... except CancelledError ... finally block. Therefore, unlike Future.cancel(), Task.cancel() does not guarantee that the Task will be cancelled, although suppressing cancellation completely is not common and is actively discouraged.

The following example illustrates how coroutines can intercept the cancellation request:

async def cancel_me():
    print('cancel_me(): before sleep')

    try:
        # Wait for 1 hour
        await asyncio.sleep(3600)
    except asyncio.CancelledError:
        print('cancel_me(): cancel sleep')
        raise
    finally:
        print('cancel_me(): after sleep')

async def main():
    # Create a "cancel_me" Task
    task = asyncio.create_task(cancel_me())

    # Wait for 1 second
    await asyncio.sleep(1)

    task.cancel()
    try:
        await task
    except asyncio.CancelledError:
        print("main(): cancel_me is cancelled now")

asyncio.run(main())

# Expected output:
#
#     cancel_me(): before sleep
#     cancel_me(): cancel sleep
#     cancel_me(): after sleep
#     main(): cancel_me is cancelled now
cancelled()

Return True if the Task is cancelled.

The Task is cancelled when the cancellation was requested with cancel() and the wrapped coroutine propagated the CancelledError exception thrown into it.

done()

Return True if the Task is done.

A Task is done when the wrapped coroutine either returned a value, raised an exception, or the Task was cancelled.

result()

Return the result of the Task.

If the Task is done, the result of the wrapped coroutine is returned (or if the coroutine raised an exception, that exception is re-raised.)

If the Task has been cancelled, this method raises a CancelledError exception.

If the Task's result isn't yet available, this method raises a InvalidStateError exception.

exception()

Return the exception of the Task.

If the wrapped coroutine raised an exception that exception is returned. If the wrapped coroutine returned normally this method returns None.

If the Task has been cancelled, this method raises a CancelledError exception.

If the Task isn't done yet, this method raises an InvalidStateError exception.

add_done_callback(callback, *, context=None)

Add a callback to be run when the Task is done.

This method should only be used in low-level callback-based code.

See the documentation of Future.add_done_callback() for more details.

remove_done_callback(callback)

コールバックリストから callback を削除します。

This method should only be used in low-level callback-based code.

See the documentation of Future.remove_done_callback() for more details.

get_stack(*, limit=None)

Return the list of stack frames for this Task.

If the wrapped coroutine is not done, this returns the stack where it is suspended. If the coroutine has completed successfully or was cancelled, this returns an empty list. If the coroutine was terminated by an exception, this returns the list of traceback frames.

フレームは常に古いものから新しい物へ並んでいます。

Only one stack frame is returned for a suspended coroutine.

The optional limit argument sets the maximum number of frames to return; by default all available frames are returned. The ordering of the returned list differs depending on whether a stack or a traceback is returned: the newest frames of a stack are returned, but the oldest frames of a traceback are returned. (This matches the behavior of the traceback module.)

print_stack(*, limit=None, file=None)

Print the stack or traceback for this Task.

This produces output similar to that of the traceback module for the frames retrieved by get_stack().

The limit argument is passed to get_stack() directly.

The file argument is an I/O stream to which the output is written; by default output is written to sys.stderr.

classmethod all_tasks(loop=None)

イベントループのすべての Task の集合を返します。

By default all tasks for the current event loop are returned. If loop is None, the get_event_loop() function is used to get the current loop.

This method is deprecated and will be removed in Python 3.9. Use the asyncio.all_tasks() function instead.

classmethod current_task(loop=None)

Return the currently running task or None.

If loop is None, the get_event_loop() function is used to get the current loop.

This method is deprecated and will be removed in Python 3.9. Use the asyncio.current_task() function instead.

Generator-based Coroutines

注釈

Support for generator-based coroutines is deprecated and is scheduled for removal in Python 3.10.

Generator-based coroutines predate async/await syntax. They are Python generators that use yield from expressions to await on Futures and other coroutines.

Generator-based coroutines should be decorated with @asyncio.coroutine, although this is not enforced.

@asyncio.coroutine

Decorator to mark generator-based coroutines.

This decorator enables legacy generator-based coroutines to be compatible with async/await code:

@asyncio.coroutine
def old_style_coroutine():
    yield from asyncio.sleep(1)

async def main():
    await old_style_coroutine()

This decorator is deprecated and is scheduled for removal in Python 3.10.

This decorator should not be used for async def coroutines.

asyncio.iscoroutine(obj)

objコルーチンオブジェクト であれば True を返します。

This method is different from inspect.iscoroutine() because it returns True for generator-based coroutines.

asyncio.iscoroutinefunction(func)

Return True if func is a coroutine function.

This method is different from inspect.iscoroutinefunction() because it returns True for generator-based coroutine functions decorated with @coroutine.