Multitasking cooperativo con co-rutinas¶
Las co-rutinas son una construcción de lenguaje diseñada para operaciones
concurrentes. Una función de co-rutina crea un objeto de co-rutina cuando es
llamada, y la persona que llama puede ejecutar el código de la función usando
el el método send()
de co-rutina. Una co-rutina puede pausar la ejecución
usando la palabra clave await
con otra co-rutina. Mientras está en pausa,
el estado de la co-rutina se mantiene, lo que le permite reanudarse donde se
dejó apagada la próxima vez que se despierta.
Comenzar una co-rutina¶
Hay formas diferentes de tener que el bucle de eventos asyncio
inicie una
co-rutina. La más simple es usar run_until_complete()
, pasándole la
co-rutina directamente.
import asyncio
async def coroutine():
print('in coroutine')
event_loop = asyncio.get_event_loop()
try:
print('starting coroutine')
coro = coroutine()
print('entering event loop')
event_loop.run_until_complete(coro)
finally:
print('closing event loop')
event_loop.close()
El primer paso es obtener una referencia al bucle de eventos. El tipo de bucle
por defecto se puede usar, o una clase de bucle específica puede ser
instanciada. En este ejemplo, se utiliza el bucle predeterminado. El método
run_until_complete()
inicia el bucle con el objeto co-rutina y detiene el
bucle cuando la co-rutina sale al retornar.
$ python3 asyncio_coroutine.py
starting coroutine
entering event loop
in coroutine
closing event loop
Valores de retorno de las co-rutinas¶
El valor de retorno de una co-rutina se devuelve al código que la comienza y la espera.
import asyncio
async def coroutine():
print('in coroutine')
return 'result'
event_loop = asyncio.get_event_loop()
try:
return_value = event_loop.run_until_complete(
coroutine()
)
print('it returned: {!r}'.format(return_value))
finally:
event_loop.close()
En este caso, run_until_complete()
también devuelve el resultado de la
co-rutina que está esperando.
$ python3 asyncio_coroutine_return.py
in coroutine
it returned: 'result'
Encadenar co-rutinas¶
Una co-rutina puede comenzar otra co-rutina y esperar los resultados. Esto facilita la descomposición de una tarea en partes reutilizables. El siguiente ejemplo tiene dos fases que deben ejecutarse en orden, pero pueden ejecutarse simultáneamente con otras operaciones.
import asyncio
async def outer():
print('in outer')
print('waiting for result1')
result1 = await phase1()
print('waiting for result2')
result2 = await phase2(result1)
return (result1, result2)
async def phase1():
print('in phase1')
return 'result1'
async def phase2(arg):
print('in phase2')
return 'result2 derived from {}'.format(arg)
event_loop = asyncio.get_event_loop()
try:
return_value = event_loop.run_until_complete(outer())
print('return value: {!r}'.format(return_value))
finally:
event_loop.close()
La palabra clave await
se usa en lugar de agregar las nuevas co-rutinas al
bucle, porque el flujo de control ya está dentro de una co-rutina gestionada
por el bucle, por lo que no es necesario indicarle al bucle que gestione las
nuevas co-rutinas.
$ python3 asyncio_coroutine_chain.py
in outer
waiting for result1
in phase1
waiting for result2
in phase2
return value: ('result1', 'result2 derived from result1')
Generadores en lugar de co-rutinas¶
Las funciones de co-rutina son un componente clave del diseño de asyncio
.
Proporcionan un lenguaje de construcción para detener la ejecución de parte de
un programa, preservando el estado de esa ejecución, y volver a entrar en el
estado en un momento posterior, que son todas capacidades importantes para un
marco de concurrencia.
Python 3.5 introdujo nuevas características de lenguaje para definir tales
co-rutinas de forma nativa usando async def
y ceder el control usando
await
, y los ejemplos para asyncio
aprovechan la nueva característica.
Las versiones anteriores de Python 3 pueden usar funciones de generador
envueltas con el decorador asyncio.coroutine()
y yield from
para lograr
el mismo efecto.
import asyncio
@asyncio.coroutine
def outer():
print('in outer')
print('waiting for result1')
result1 = yield from phase1()
print('waiting for result2')
result2 = yield from phase2(result1)
return (result1, result2)
@asyncio.coroutine
def phase1():
print('in phase1')
return 'result1'
@asyncio.coroutine
def phase2(arg):
print('in phase2')
return 'result2 derived from {}'.format(arg)
event_loop = asyncio.get_event_loop()
try:
return_value = event_loop.run_until_complete(outer())
print('return value: {!r}'.format(return_value))
finally:
event_loop.close()
El ejemplo anterior reproduce asyncio_coroutine_chain.py
usando funciones
generadoras en lugar de co-rutinas nativos.
$ python3 asyncio_generator.py
in outer
waiting for result1
in phase1
waiting for result2
in phase2
return value: ('result1', 'result2 derived from result1')