Ejecutar tareas concurrentemente

Las tareas son una de las principales formas de interactuar con el bucle de eventos. Las tareas envuelven a las co-rutinas y las siguen cuándo están listas. Tareas son subclases de Future, por lo que otras co-rutinas pueden esperar o ellas y cada una tiene un resultado que se puede recuperar después de que la tarea completa.

Comenzar una tarea

Para iniciar una tarea, usa create_task() para crear una instancia Task. La tarea resultante se ejecutará como parte de las operaciones simultáneas gestionadas por el bucle de eventos siempre que el bucle esté corriendo y la co-rutina no retorne.

asyncio_create_task.py
import asyncio


async def task_func():
    print('in task_func')
    return 'the result'


async def main(loop):
    print('creating task')
    task = loop.create_task(task_func())
    print('waiting for {!r}'.format(task))
    return_value = await task
    print('task completed {!r}'.format(task))
    print('return value: {!r}'.format(return_value))


event_loop = asyncio.get_event_loop()
try:
    event_loop.run_until_complete(main(event_loop))
finally:
    event_loop.close()

Este ejemplo espera a que la tarea devuelva un resultado antes de que la función main() termine.

$ python3 asyncio_create_task.py

creating task
waiting for <Task pending coro=<task_func() running at
asyncio_create_task.py:12>>
in task_func
task completed <Task finished coro=<task_func() done, defined at
asyncio_create_task.py:12> result='the result'>
return value: 'the result'

Cancelar una tarea

Al retener el objeto Task devuelto de create_task(), es posible cancelar la operación de la tarea antes de que se complete.

asyncio_cancel_task.py
import asyncio


async def task_func():
    print('in task_func')
    return 'the result'


async def main(loop):
    print('creating task')
    task = loop.create_task(task_func())

    print('canceling task')
    task.cancel()

    print('canceled task {!r}'.format(task))
    try:
        await task
    except asyncio.CancelledError:
        print('caught error from canceled task')
    else:
        print('task result: {!r}'.format(task.result()))


event_loop = asyncio.get_event_loop()
try:
    event_loop.run_until_complete(main(event_loop))
finally:
    event_loop.close()

Este ejemplo crea y luego cancela una tarea antes de iniciar el bucle de eventos. El resultado es una excepción CancelledError de run_until_complete().

$ python3 asyncio_cancel_task.py

creating task
canceling task
canceled task <Task cancelling coro=<task_func() running at
asyncio_cancel_task.py:12>>
caught error from canceled task

Si una tarea se cancela mientras está esperando otra operación concurrente, la tarea es notificada de su cancelación al elevar una excepción CancelledError en el punto donde está esperando.

asyncio_cancel_task2.py
import asyncio


async def task_func():
    print('in task_func, sleeping')
    try:
        await asyncio.sleep(1)
    except asyncio.CancelledError:
        print('task_func was canceled')
        raise
    return 'the result'


def task_canceller(t):
    print('in task_canceller')
    t.cancel()
    print('canceled the task')


async def main(loop):
    print('creating task')
    task = loop.create_task(task_func())
    loop.call_soon(task_canceller, task)
    try:
        await task
    except asyncio.CancelledError:
        print('main() also sees task as canceled')


event_loop = asyncio.get_event_loop()
try:
    event_loop.run_until_complete(main(event_loop))
finally:
    event_loop.close()

Atrapar la excepción brinda la oportunidad de limpiar el trabajo ya hecho, si es necesario.

$ python3 asyncio_cancel_task2.py

creating task
in task_func, sleeping
in task_canceller
canceled the task
task_func was canceled
main() also sees task as canceled

Crear tareas desde co-rutinas

La función ensure_future() devuelve una Task vinculada a la ejecución de una co-rutina. Esa instancia Task puede entonces ser pasada a otro código, que puede esperarla sin saber cómo la co-rutina original fue construida o llamada.

asyncio_ensure_future.py
import asyncio


async def wrapped():
    print('wrapped')
    return 'result'


async def inner(task):
    print('inner: starting')
    print('inner: waiting for {!r}'.format(task))
    result = await task
    print('inner: task returned {!r}'.format(result))


async def starter():
    print('starter: creating task')
    task = asyncio.ensure_future(wrapped())
    print('starter: waiting for inner')
    await inner(task)
    print('starter: inner returned')


event_loop = asyncio.get_event_loop()
try:
    print('entering event loop')
    result = event_loop.run_until_complete(starter())
finally:
    event_loop.close()

Ten en cuenta que la rutina asignada a ensure_future() no se inicia hasta que algo use await para permitir que se ejecute.

$ python3 asyncio_ensure_future.py

entering event loop
starter: creating task
starter: waiting for inner
inner: starting
inner: waiting for <Task pending coro=<wrapped() running at
asyncio_ensure_future.py:12>>
wrapped
inner: task returned 'result'
starter: inner returned