unity definicion corrutinas python python-3.x asynchronous concurrency python-asyncio

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 ejecutado some_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ón async() o el método BaseEventLoop.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?