18.5.2. Tasks and coroutines

18.5.2.1. Coroutines

A coroutine is a generator that follows certain conventions. For documentation purposes, all coroutines should be decorated with @asyncio.coroutine, but this cannot be strictly enforced.

Coroutines use the yield from syntax introduced in PEP 380, instead of the original yield syntax.

The word “coroutine”, like the word “generator”, is used for two different (though related) concepts:

  • The function that defines a coroutine (a function definition decorated with @asyncio.coroutine). If disambiguation is needed we will call this a coroutine function (iscoroutinefunction() returns True).
  • The object obtained by calling a coroutine function. This object represents a computation or an I/O operation (usually a combination) that will complete eventually. If disambiguation is needed we will call it a coroutine object (iscoroutine() returns True).

Things a coroutine can do:

  • result = yield from future – suspends the coroutine until the future is done, then returns the future’s result, or raises an exception, which will be propagated. (If the future is cancelled, it will raise a CancelledError exception.) Note that tasks are futures, and everything said about futures also applies to tasks.
  • result = yield from coroutine – wait for another coroutine to produce a result (or raise an exception, which will be propagated). The coroutine expression must be a call to another coroutine.
  • return expression – produce a result to the coroutine that is waiting for this one using yield from.
  • raise exception – raise an exception in the coroutine that is waiting for this one using yield from.

Calling a coroutine does not start its code running – it is just a generator, and the coroutine object returned by the call is really a generator object, which doesn’t do anything until you iterate over it. In the case of a coroutine object, there are two basic ways to start it running: call yield from coroutine from another coroutine (assuming the other coroutine is already running!), or convert it to a Task.

Coroutines (and tasks) can only run when the event loop is running.

@asyncio.coroutine

Decorator to mark coroutines.

If the coroutine is not yielded from before it is destroyed, an error message is logged. See Detect coroutines never scheduled.

Note

In this documentation, some methods are documented as coroutines, even if they are plain Python functions returning a Future. This is intentional to have a freedom of tweaking the implementation of these functions in the future. If such a function is needed to be used in a callback-style code, wrap its result with async().

18.5.2.1.1. Example: “Hello World” coroutine

Print "Hello World" every two seconds using a coroutine:

import asyncio

@asyncio.coroutine
def greet_every_two_seconds():
    while True:
        print('Hello World')
        yield from asyncio.sleep(2)

loop = asyncio.get_event_loop()
loop.run_until_complete(greet_every_two_seconds())

18.5.2.1.2. Example: Chain coroutines

Example chaining coroutines:

import asyncio

@asyncio.coroutine
def compute(x, y):
    print("Compute %s + %s ..." % (x, y))
    yield from asyncio.sleep(1.0)
    return x + y

@asyncio.coroutine
def print_sum(x, y):
    result = yield from compute(x, y)
    print("%s + %s = %s" % (x, y, result))

loop = asyncio.get_event_loop()
loop.run_until_complete(print_sum(1, 2))
loop.close()

compute() is chained to print_sum(): print_sum() coroutine waits until compute() is completed before returning its result.

Sequence diagram of the example:

../_images/tulip_coro.png

The “Task” is created by the BaseEventLoop.run_until_complete() method when it gets a coroutine object instead of a task.

The diagram shows the control flow, it does not describe exactly how things work internally. For example, the sleep coroutine creates an internal future which uses BaseEventLoop.call_later() to wake up the task in 1 second.

18.5.2.2. InvalidStateError

exception asyncio.InvalidStateError

The operation is not allowed in this state.

18.5.2.3. Future

class asyncio.Future(*, loop=None)

This class is almost compatible with concurrent.futures.Future.

Differences:

cancel()

Cancel the future and schedule callbacks.

If the future is already done or cancelled, return False. Otherwise, change the future’s state to cancelled, schedule the callbacks and return True.

cancelled()

Return True if the future was cancelled.

done()

Return True if the future is done.

Done means either that a result / exception are available, or that the future was cancelled.

result()

Return the result this future represents.

If the future has been cancelled, raises CancelledError. If the future’s result isn’t yet available, raises InvalidStateError. If the future is done and has an exception set, this exception is raised.

exception()

Return the exception that was set on this future.

The exception (or None if no exception was set) is returned only if the future is done. If the future has been cancelled, raises CancelledError. If the future isn’t done yet, raises InvalidStateError.

add_done_callback(fn)

Add a callback to be run when the future becomes done.

The callback is called with a single argument - the future object. If the future is already done when this is called, the callback is scheduled with call_soon().

remove_done_callback(fn)

Remove all instances of a callback from the “call when done” list.

Returns the number of callbacks removed.

set_result(result)

Mark the future done and set its result.

If the future is already done when this method is called, raises InvalidStateError.

set_exception(exception)

Mark the future done and set an exception.

If the future is already done when this method is called, raises InvalidStateError.

18.5.2.3.1. Example: Future with run_until_complete()

Example combining a Future and a coroutine function:

import asyncio

@asyncio.coroutine
def slow_operation(future):
    yield from asyncio.sleep(1)
    future.set_result('Future is done!')

loop = asyncio.get_event_loop()
future = asyncio.Future()
asyncio.Task(slow_operation(future))
loop.run_until_complete(future)
print(future.result())
loop.close()

The coroutine function is responsible of the computation (which takes 1 second) and it stores the result into the future. The run_until_complete() method waits for the completion of the future.

Note

The run_until_complete() method uses internally the add_done_callback() method to be notified when the future is done.

18.5.2.3.2. Example: Future with run_forever()

The previous example can be written differently using the Future.add_done_callback() method to describe explicitly the control flow:

import asyncio

@asyncio.coroutine
def slow_operation(future):
    yield from asyncio.sleep(1)
    future.set_result('Future is done!')

def got_result(future):
    print(future.result())
    loop.stop()

loop = asyncio.get_event_loop()
future = asyncio.Future()
asyncio.Task(slow_operation(future))
future.add_done_callback(got_result)
try:
    loop.run_forever()
finally:
    loop.close()

In this example, the future is responsible to display the result and to stop the loop.

Note

The “slow_operation” coroutine object is only executed when the event loop starts running, so it is possible to add a “done callback” to the future after creating the task scheduling the coroutine object.

18.5.2.4. Task

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

A coroutine object wrapped in a Future. Subclass of Future.

classmethod all_tasks(loop=None)

Return a set of all tasks for an event loop.

By default all tasks for the current event loop are returned.

classmethod current_task(loop=None)

Return the currently running task in an event loop or None.

By default the current task for the current event loop is returned.

None is returned when called not in the context of a Task.

get_stack(self, *, limit=None)

Return the list of stack frames for this task’s coroutine.

If the coroutine is active, 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.

The frames are always ordered from oldest to newest.

The optional limit gives the maximum number of frames to return; by default all available frames are returned. Its meaning 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.)

For reasons beyond our control, only one stack frame is returned for a suspended coroutine.

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

Print the stack or traceback for this task’s coroutine.

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(). The file argument is an I/O stream to which the output goes; by default it goes to sys.stderr.

18.5.2.4.1. Example: Parallel execution of tasks

Example executing 3 tasks (A, B, C) in parallel:

import asyncio

@asyncio.coroutine
def factorial(name, number):
    f = 1
    for i in range(2, number+1):
        print("Task %s: Compute factorial(%s)..." % (name, i))
        yield from asyncio.sleep(1)
        f *= i
    print("Task %s: factorial(%s) = %s" % (name, number, f))

tasks = [
    asyncio.Task(factorial("A", 2)),
    asyncio.Task(factorial("B", 3)),
    asyncio.Task(factorial("C", 4))]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

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

A task is automatically scheduled for execution when it is created. The event loop stops when all tasks are done.

18.5.2.5. Task functions

Note

In the functions below, the optional loop argument allows to explicitly set the event loop object used by the underlying task or coroutine. If it’s not provided, the default event loop is used.

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

Return an iterator whose values, when waited for, are Future instances.

Raises TimeoutError if the timeout occurs before all Futures are done.

Example:

for f in as_completed(fs):
    result = yield from f  # The 'yield from' may raise
    # Use result

Note

The futures f are not necessarily members of fs.

asyncio.async(coro_or_future, *, loop=None)

Wrap a coroutine object in a future.

If the argument is a Future, it is returned directly.

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

Return a future aggregating results from the given coroutine objects or futures.

All futures must share the same event loop. If all the tasks are done successfully, the returned future’s result is the list of results (in the order of the original sequence, not necessarily the order of results arrival). If return_exceptions is True, exceptions in the tasks are treated the same as successful results, and gathered in the result list; otherwise, the first raised exception will be immediately propagated to the returned future.

Cancellation: if the outer Future is cancelled, all children (that have not completed yet) are also cancelled. If any child is cancelled, this is treated as if it raised CancelledError – the outer Future is not cancelled in this case. (This is to prevent the cancellation of one child to cause other children to be cancelled.)

asyncio.iscoroutine(obj)

Return True if obj is a coroutine object.

asyncio.iscoroutinefunction(obj)

Return True if func is a decorated coroutine function.

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

Create a coroutine that completes after a given time (in seconds). If result is provided, it is produced to the caller when the coroutine completes.

The resolution of the sleep depends on the granularity of the event loop.

asyncio.shield(arg, *, loop=None)

Wait for a future, shielding it from cancellation.

The statement:

res = yield from shield(something())

is exactly equivalent to the statement:

res = yield from something()

except that if the coroutine containing it is cancelled, the task running in something() is not cancelled. From the point of view of something(), the cancellation did not happen. But its caller is still cancelled, so the yield-from expression still raises CancelledError. Note: If something() is cancelled by other means this will still cancel shield().

If you want to completely ignore cancellation (not recommended) you can combine shield() with a try/except clause, as follows:

try:
    res = yield from shield(something())
except CancelledError:
    res = None
asyncio.wait(futures, *, loop=None, timeout=None, return_when=ALL_COMPLETED)

Wait for the Futures and coroutine objects given by the sequence futures to complete. Coroutines will be wrapped in Tasks. Returns two sets of Future: (done, pending).

timeout can be used to control the maximum number of seconds to wait before returning. timeout can be an int or float. If timeout is not specified or None, there is no limit to the wait time.

return_when indicates when this function should return. It must be one of the following constants of the concurrent.futures module:

Constant Description
FIRST_COMPLETED The function will return when any future finishes or is cancelled.
FIRST_EXCEPTION The function will return when any future finishes by raising an exception. If no future raises an exception then it is equivalent to ALL_COMPLETED.
ALL_COMPLETED The function will return when all futures finish or are cancelled.

This function is a coroutine.

Usage:

done, pending = yield from asyncio.wait(fs)

Note

This does not raise TimeoutError! Futures that aren’t done when the timeout occurs are returned in the second set.

asyncio.wait_for(fut, timeout, *, loop=None)

Wait for the single Future or coroutine object to complete, with timeout. If timeout is None, block until the future completes.

Coroutine will be wrapped in Task.

Returns result of the Future or coroutine. When a timeout occurs, it cancels the task and raises TimeoutError. To avoid the task cancellation, wrap it in shield().

This function is a coroutine.

Usage:

result = yield from asyncio.wait_for(fut, 60.0)