Asyncio Tasks and Coroutines

Asyncio Tasks and Coroutines

By Marcelo Fernandes Sep 11, 2017

Coroutines

Coroutines used with asyncio may be implemented using the async def statement, or by using generators. The async def type of coroutine was added in Python 3.5, and is recommended.
Generator-based coroutines should be decorated with @asyncio.coroutine, although this is not strictly enforced. The decorator enables compatibility with async def coroutines, and also serves as documentation. Generator-based coroutines use the yield from syntax, instead of the yield syntax.


Few things a coroutine can do:

  • result = await future or 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 canceled, it will raise a CancelledError exception). Note that tasks are futures and everything said about futures also applies to tasks.
  • result = await coroutine or result = yield from coroutine - wait for another coroutine to produce a results (or raise an exception, which will be propagated). The coroutine expression must be a call to another coroutine.
  • return expression - produces a result to the coroutine that is waiting for this one using await or yield from
  • raise exception - raises an exception in the coroutine that is waiting for this one using await or yield from

@asyncio.coroutine

Decorator to mark generator-based coroutines. This enables the generator use yield from to call async def coroutines, and also enables the generator to be called by async def coroutines, for instance using an await expression.

There is no need to decorate async def coroutines themselves.


Example: Hello World Coroutine


import asyncio

async def hello_world():
    print("Hello World!")

loop = asyncio.get_event_loop()
# Blocking call which returns when the hello_world() coroutine is done
loop.run_until_complete(hello_world())
loop.close()


Example: Coroutine displaying the current date


import asyncio
import datetime

async def display_date(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)

loop = asyncio.get_event_loop()
# Blocking call which returns when the display_date() coroutine is done
loop.run_until_complete(display_date(loop))
loop.close()



Future

class asyncio.Future(*, loop=None)

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()

Returns a boolean.



done()

Returns a boolean


result()

Return the result this future represents.
If the future has been cancelled, raises a CancelledError, if the future's result ins't yet available, raises InvalidStateError. If the furue is done and has an exception set, this exception is raised.


exception()

Return the exception that was set on this future.


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()

use functools.partial to pass parameters to the callback. Example: fut.add_done_callback(functools.partial(print, "Future:", flush=True))


remove_done_callback(fn)

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


set_result(result)

Mark the future done and set its result


set_exception(exception)

Mark the future done and set an exception.



Example: Future with run_until_complete()


import asyncio

async def slow_operation(future):
    await asyncio.sleep(1)
    future.set_result('Future is done!')

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



Taks

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

Schedule the execution of a coroutine: wrap it in a future. A task is a subclass of Future
A task is responsible for executing a coroutine object in an event loop. If the wrapped coroutine yields from a future, the task suspends the execution of the wrapped coroutine and waits for the completion of the future. When the future is done, the execution of the wrapped coroutine restarts with the result or the exception of the future.
Event loops use cooperative scheduling: an event loop only runs one task at a time. Other tasks may run in parallel if other event loops are running in different threads. While a task waits for the completion of a future, the event loop executes a new task.
Don't directely create Task instances: use the ensure_future() function.
This class is not thread safe.


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


current_task(loop=None)

Return the currently running task in an event loop or None


cancel()

Request that this task cancel itself.


get_stack(*, limit=None)

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


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

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


Example: Parallel execution of tasks


import asyncio

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

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(
    factorial("A", 2),
    factorial("B", 3),
    factorial("C", 4),
))
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


Notes


References:


link 1