python - definicion - Rendimiento de corrutina vs rendimiento de la tarea
corrutinas definicion (3)
Guido van Rossum, en su discurso en 2014 en Tulip / Asyncio muestra la diapositiva :
Tareas vs corutinas
Comparar:
- res = rendimiento de some_coroutine (...)
- res = rendimiento de Tarea (some_coroutine (...))
La tarea puede progresar sin esperarla
- Como iniciar sesión mientras esperas algo más
- es decir, rendimiento de
Y estoy completamente perdido el punto.
Desde mi punto de vista, ambos constructos son idénticos:
En caso de coroutine simple: se programa, por lo que la tarea se crea de todos modos, porque el programador opera con Tareas, luego coroutine caller coroutine se suspende hasta que se realiza y luego se vuelve libre para continuar la ejecución.
En caso de Task
- De todos modos - la nueva tarea es programada y la llamada entrante espera su finalización.
¿Cuál es la diferencia en la forma en que se ejecuta el código en ambos casos y qué impacto debe tener ese desarrollador en la práctica?
PD
Los enlaces a fuentes autorizadas (GvR, PEP, documentos, notas de desarrolladores centrales) serán muy apreciados.
Como se describe en PEP 380, el documento PEP aceptado que introdujo el rendimiento de, la expresión res = yield from f()
proviene de la idea del siguiente ciclo:
for res in f():
yield res
Con esto, las cosas se vuelven muy claras: si f()
es some_coroutine()
, entonces se ejecuta la coroutine. Por otro lado, si f()
es Task(some_coroutine())
, en su Task.__init__
se ejecuta la Task.__init__
. some_coroutine()
no se ejecuta, solo el generador recién creado se pasa como primer argumento a la Task.__init__
.
Conclusión:
-
res = yield from some_coroutine()
=> coroutine continúa la ejecución y devuelve el siguiente valor -
res = yield from Task(some_coroutine())
=> se crea una nueva tarea, que almacena un objeto generador no ejecutadosome_coroutine()
.
El objetivo de usar asyncio.Task(coro())
es para los casos en los que no se desea esperar explícitamente a coro
, pero se desea ejecutar coro
en segundo plano mientras se esperan otras tareas. Eso es lo que significa la diapositiva de Guido por
[A] La
Task
puede progresar sin esperar ... mientras esperes algo más
Considera este ejemplo:
import asyncio
@asyncio.coroutine
def test1():
print("in test1")
@asyncio.coroutine
def dummy():
yield from asyncio.sleep(1)
print("dummy ran")
@asyncio.coroutine
def main():
test1()
yield from dummy()
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Salida:
dummy ran
Como puede ver, test1
nunca se ejecutó realmente, porque no llamamos explícitamente a yield from
en él.
Ahora, si usamos asyncio.async
para envolver una instancia de Task
alrededor de test1
, el resultado es diferente:
import asyncio
@asyncio.coroutine
def test1():
print("in test1")
@asyncio.coroutine
def dummy():
yield from asyncio.sleep(1)
print("dummy ran")
@asyncio.coroutine
def main():
asyncio.async(test1())
yield from dummy()
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Salida:
in test1
dummy ran
Entonces, realmente no hay ninguna razón práctica para usar el yield from asyncio.async(coro())
, ya que es más lento que el yield from coro()
sin ningún beneficio; introduce la sobrecarga de agregar coro
al programador asyncio
interno, pero eso no es necesario, ya que usar el yield from
garantías de que coro
se va a ejecutar, de todos modos. Si solo quieres llamar a una corrutina y esperar a que termine, solo yield from
directamente la corrutina.
Nota al margen:
Estoy usando asyncio.async
* en lugar de Task
directamente porque los documentos lo recomiendan :
No cree directamente instancias de
Task
: use la funciónasync()
o el métodoBaseEventLoop.create_task()
.
* Tenga en cuenta que a partir de Python 3.4.4, asyncio.async
está en desuso en favor de asyncio.ensure_future
.
Para el lado de la llamada, el yield from coroutine()
co-rutina yield from coroutine()
siente como una llamada de función (es decir, volverá a tener el control cuando finalice coroutine ()).
yield from Task(coroutine())
por otro lado se parece más a la creación de un nuevo hilo. Task()
vuelve casi al instante y es muy probable que la persona que llama recupere el control antes de que coroutine()
la coroutine()
.
La diferencia entre f()
y th = threading.Thread(target=f, args=()); th.start(); th.join()
th = threading.Thread(target=f, args=()); th.start(); th.join()
th = threading.Thread(target=f, args=()); th.start(); th.join()
es obvio, ¿verdad?