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
Taskpuede 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?