Guia Asyncio
Guia Asyncio
Contents
1 Introducción 1
2 Síntaxis de asyncio 3
2.1 ¿Cómo utilizar una función asíncrona? . . . . . . . . . . . . . 5
2.1.1 El modo simple. . . . . . . . . . . . . . . . . . . . . . 6
2.1.2 Modo detallado. . . . . . . . . . . . . . . . . . . . . . 6
2.2 Ejecutar varias corrutinas dentro de un mismo loop . . . . . . 7
2.2.1 Llamar una corrutina dentro de otra . . . . . . . . . . 7
2.2.2 Llamar varias corrutinas asignadas a un loop . . . . . 8
2.2.3 Múltiples tareas con llamadas a await internamente. . 9
5 Proyectos de Interés 23
6 Conocimiento recomendado 24
1 Introducción
Dentro de todos los distintos módulos de Python, el llamado asyncio (Doc-
umentación) por Asynchronous I/O (entrada/salida asincrónica) parece
ser uno de aquellos que abre todo un abanico de posibilidades ya que per-
mite un uso intensivo de los recursos del computador.
1
Permite acercarnos a la realidad de los eventos que ocurren en el contexto
operacional de estas tecnologías. Entrega un punto de conexión a aquellos
factores que no podemos controlar con nuestro programa pero que si necesi-
tamos acceder a estos. Por eso es que cambia el panorama o el paradigma
de desarrollo usual de un programa tradicional y la dificultad se torna expo-
nencial :(.
Pero todo tiene solución. La fortuna de que tenemos con python es que
provee las herramientas necesarias para poder crear valiosos trozos de código
que operan de manera asíncrona, y esta guia será la que te introducirá en
los principales conceptos a considerar para el uso de este módulo y creación
de asombroso código.
Si sucede que necesitamos acceder a una serie de eventos que no sabemos
cuando ocurren, pero que cuando ocurran debemos capturar esa información
y procesarla, entonces es recomendable implementar el software con asyncio.
Esto nos permitirá gestionar de mejor manera a que si tuvieramos una pila
de eventos a revisar secuencialmente, unos ocurrirían antes que otros, pero
para atender el que primero ocurra deberemos atender al que se active en el
orden correspondiente.
Para aclarar, cuando estamos atendiendo una serie de eventos de manera
secuencial. Sean estos respuesta desde una base de datos, información que
envían fuentes de datos o sensores, posteos de multiples personas, si los
vamos capturando de manera secuencial tendremos lo siguiente.
2
hará nada, el tiempo en que se atienda un evento será mayor y, por último,
será en conjunto menos eficiente.
De otra manera, cuando atendemos una serie de eventos, asignadas me-
diante tareas. Los mismos eventos del gráfico anterior se pueden ver de la
manera siguiente:
2 Síntaxis de asyncio
Python provee una serie de tipos de funciones que se caracterizan por como
entregan su resultado.
función tradicional
3
Es aquella clásica, que tiene una entrada y retorna algo (o solo hace algo),
ejecutada de manera secuencial.
def imprimir(msg):
print(msg)
def suma(a,b):
return a+b
función generador
4
await para usar corrutinas dentro de currutinas
import asyncio
• Comienza la ejecución
• Espera un segundo (libera el procesador en tanto)
• Imprimir "Hola #numero"
• Lo hace tres veces.
loop es, por decir de alguna manera, el contexto o universo dentro del cual
se hace posible ejecutar las corrutinas. Como su nombre lo indica, y la
implementación, una máquina que realiza una serie de procedimientos
repetidamente que habilita las funcionalidades asíncronas.
corrutinas son las funciones asíncronas enunciadas, dentro de cuales se eje-
cuta código de manera secuencial. Estas se ejecutas solamente cuando
están dentro de un loop.
Futures Son un conjunto de funciones y objetos que proveen conectividad
entre la ejecución del loop a bajo nivel y las llamadas a las funciones
asíncronas en alto nivel.
5
Task Es una clase objecto que permite asignar la ejecución de una corrutina.
Está implementada como un objeto que hereda de Futures, esto quiere
decir que todas las funcionalidades habilitadas para esta clase padre o
madre, también están disponibles para Task.
import asyncio
from corrutinas import holacoro
# Cerrar el loop
loop.close()
import asyncio
from corrutinas import holacoro
6
# Quiero ejecutar holacoro
# Debo crear una tarea
# Asignarla al "trabajador(procesador)"
# Encargado del loop
task = None
if asyncio.iscoroutinefunction(holacoro):
print("holacoro es corrutina, se crea tarea")
# se crea la corrutina
print("¿Qué es la función holacoro?")
print(holacoro)
coro = holacoro()
# se agenda para el loop
print("¿Qué es coro?")
print(coro)
task = asyncio.ensure_future(coro)
else:
print("holacoro no es corrutina, no se asigna")
if task:
loop.run_until_complete(task)
loop.close()
7
from datetime import datetime
import asyncio
loop = asyncio.get_event_loop()
loop.run_until_complete(imprimir())
loop.close()
8
if __name__ == "__main__":
tasks=[
asyncio.ensure_future(holacoro()),
asyncio.ensure_future(chaocoro())
]
loop = asyncio.get_event_loop()
#creamos tarea y la asociamos al loop, ejecutandola
loop.run_until_complete(
asyncio.gather(*tasks)
)
Esto modo de hacer también tiene sus limitaciones que están por fuera de
su diseño. Se da el caso de que si fuera necesario trabajar con datos en tiempo
real generados por fuentes de datos (sensores) de manera consecutiva. Hay
capacidad que cada trabajador puede atender de manera asíncrona. Cuando
eso sucede es necesario trabajar, además, con múltiples procesos.
9
En este caso, se observa lo siguiente:
• En el await de T0 se ejecuta T1
• Retoma T0 nuevamente.
10
3 Un async while artesanal
Cuando necesitamos una mayor funcionalidad asíncrona, en que necesitemos
que cada corrutina de opere de manera independiente y en un loop, no basta
con poner un while clásico como estructura de control. Tiene que ir un poco
por sobre eso y debe estar a nivel de los objetos tareas Tasks.
La clave de esto es el método add_done_callback, que permite definir
una función que ejecuta nuevamente la misma corrutina.
import asyncio
def renew(*args):
task = asyncio.ensure_future(holacoro())
task.add_done_callback(renew)
task=asyncio.ensure_future(holacoro())
task.add_done_callback(renew)
loop=asyncio.get_event_loop()
try:
loop.run_forever()
except KeyboardInterrupt:
print('Loop stopped')
import asyncio
import functools
11
print("Hola %d" % v)
await asyncio.sleep(1)
return v+1
args=[1]
task=asyncio.ensure_future(coromask(holacoro,args))
task.add_done_callback(
functools.partial(renew,
task,
coromask,
holacoro))
loop=asyncio.get_event_loop()
try:
loop.run_forever()
except KeyboardInterrupt:
print('Loop stopped')
Con esto, nos damos cuenta que debemos tratar los argumentos de en-
trada así como también las salidas. Esto porque en la próxima iteración tam-
bién debemos tener argumentos que vengan de alguna parte. Estos pueden
ser:
12
• Argumentos que son los que sale de la iteración anterior
import asyncio
import functools
loop=asyncio.get_event_loop()
args=[1]
13
task1=asyncio.ensure_future(coromask(holacoro,args))
task1.add_done_callback(
functools.partial(renew, task1, coromask, holacoro))
args2=[1,2]
task2=asyncio.ensure_future(coromask_suma(sumacoro,args2))
task2.add_done_callback(
functools.partial(renew, task2, coromask_suma, sumacoro))
try:
loop.run_forever()
except KeyboardInterrupt:
print('Loop stopped')
De manera más general, se definen las funciones que procesan los argu-
mentos para nuestras funciones asíncronas.
import asyncio
import functools
#every coroutine
14
async def coromask(coro, args, fargs):
_in=args
obtained=await coro(*args)
result=fargs(_in, obtained)
return result
loop=asyncio.get_event_loop()
args=[1]
task1=loop.create_task(
coromask(holacoro,args,fargs_holacoro))
task1.add_done_callback(
functools.partial(renew, task1, holacoro, fargs_holacoro))
args2=[1,2]
task2=loop.create_task(
coromask(sumacoro,args2,fargs_sumacoro))
task2.add_done_callback(
functools.partial(renew, task2, sumacoro, fargs_sumacoro))
try:
loop.run_forever()
except KeyboardInterrupt:
print('Loop stopped')
import asyncio
import functools
15
#every coroutine
async def coromask(coro, args, fargs):
try:
_in = args
msg = ("Coromask args %s in coro %s" %(args, coro) )
obtained = await coro(*args)
if isinstance(obtained, Exception):
raise Exception()
else:
result = fargs(_in, obtained)
return result
except Exception:
print(msg)
raise Exception
16
En resumen, el esquema que nos habilita nuestro async while es el que
viene a continuación.
Creamos una clase Engine que contenga una serie de métodos asín-
cronos. De la manera siguiente.
17
Además, cada método asíncrono genera un resultado que se comunica
mediante una cola o Queue a otro de los métodos. Por lo que cada iteración
tendrá resultados diferentes que dependen del resultado de la ejecución de
las otras funciones.
Las Queue, son objetos que permiten enviar información de un punto
a otro. Operan como si fuera un cable de comunicación. De esta manera,
desde un punto se envía un dato que se recibe desde otro punto.
import asyncio
import functools
import math
from networktools.time import timestamp
from networktools.colorprint import gprint, \
bprint, rprint
from tasktools.taskloop import coromask,\
renew, simple_fargs, simple_fargs_out
def read_queue(queue):
result=[]
if not queue.empty():
for m in range(queue.qsize()):
result.append(queue.get())
queue.task_done()
return result
18
_out=[b,_in[1]]
return _out
class Engine(object):
def __init__(self, *args, **kwargs):
self.a=kwargs['a']
self.b=kwargs['b']
self.w=kwargs['w']
self.mode=kwargs['mode']
#colores::::
#red: suma
#blue: resta
#green: seno
19
r=read_queue(queue_resta)
s=read_queue(queue_suma)
result=0
print(r)
print(s)
if s and r:
amplitude=s[0]/r[0]
ts=timestamp()
result = amplitude * math.sin(self.w*ts)
gprint(result)
await asyncio.sleep(2)
return result
20
args= [delta, queue]
# Create instances
task=loop.create_task(
coromask(
self.resta,
args,
resta_fargs)
)
task.add_done_callback(
functools.partial(
renew,
task,
self.resta,
resta_fargs)
)
if not loop.is_running() and self.mode=='mprocess':
loop.run_forever()
pass
21
4.1 Ejecución en un proceso.
Nuestra clase Engine puede ser llamada desde un solo trabajador (loop) de
la manera siguiente.
import asyncio
from engine import Engine
if __name__ == "__main__":
loop = asyncio.get_event_loop()
queue_suma=asyncio.Queue()
queue_resta=asyncio.Queue()
a=2
b=3
w=.05
engine=Engine(a=a,b=b,w=w, mode='async')
engine.suma_task(queue_suma)
engine.resta_task(queue_resta)
engine.seno_task(queue_suma,queue_resta)
loop.run_forever()
import asyncio
import functools
import concurrent.futures
from engine import Engine
from multiprocessing import Manager, Queue
if __name__ == "__main__":
loop = asyncio.get_event_loop()
manager=Manager()
queue_suma=manager.Queue()
queue_resta=manager.Queue()
22
a=2
b=3
w=.05
engine=Engine(a=a,b=b,w=w, mode='mprocess')
with concurrent.futures.ProcessPoolExecutor() as executor:
def suma_process():
print(engine)
engine.suma_task(queue_suma)
def resta_process():
print(engine)
engine.resta_task(queue_resta)
def seno_process():
print(engine)
engine.seno_task(queue_suma,queue_resta)
loop.run_in_executor(
executor,
#functools.partial(engine.suma_task,queue_suma)
suma_process
)
loop.run_in_executor(
executor,
resta_process
)
loop.run_in_executor(
executor,
seno_process
)
#await engine.resta_task(queue_resta)
#await engine.seno_task(queue_suma, queue_resta)
loop.run_forever()
5 Proyectos de Interés
Entre los proyectos de interés realizados por quien realizó esta guia, con
asyncio puedes ver:
23
tareas. Gitlab
6 Conocimiento recomendado
David Beaz http://www.dabeaz.com/
WillysCave Blog
MichaelKuty http://michaelkuty.github.io/vertx-gdg/#/
24