Manejo de los tiempos de espera con asyncio

truco en total:

Exención de responsabilidad: esta es mi primera vez experimentar con el asynciomódulo.

Estoy usando asyncio.waitla siguiente manera para tratar de apoyar una función de tiempo de espera para todos los resultados de un conjunto de tareas asíncronas. Esto es parte de una biblioteca más grande, así que estoy omitiendo algo de código irrelevante.

Tenga en cuenta que la biblioteca ya es compatible con la presentación de tareas y el uso de los tiempos de espera con ThreadPoolExecutors y ProcessPoolExecutors, así que no estoy realmente interesado en sugerencias para utilizar aquellos en su lugar o preguntas acerca de por qué estoy haciendo esto con asyncio. En al código ...

import asyncio
from contextlib import suppress

... 

class AsyncIOSubmit(Node):
    def get_results(self, futures, timeout=None):
        loop = asyncio.get_event_loop()
        finished, unfinished = loop.run_until_complete(
            asyncio.wait(futures, timeout=timeout)
        )
        if timeout and unfinished:
            # Code options in question would go here...see below.
            raise asyncio.TimeoutError

Al principio no estaba preocupado por la cancelación de tareas pendientes en el tiempo de espera, pero luego me dieron el aviso Task was destroyed but it is pending!de salida del programa o loop.close. Después de investigar un poco me encontré con varios modos de cancelar tareas y esperar a que en realidad ser cancelado:

Opción 1:

[task.cancel() for task in unfinished]
for task in unfinished:
    with suppress(asyncio.CancelledError):
        loop.run_until_complete(task)

Opcion 2:

[task.cancel() for task in unfinished]
loop.run_until_complete(asyncio.wait(unfinished))

Opción 3:

# Not really an option for me, since I'm not in an `async` method
# and don't want to make get_results an async method.
[task.cancel() for task in unfinished]
for task in unfinished:
    await task

Opción 4:

Una especie de bucle while como en esta respuesta. Parece que mis otras opciones son mejores, pero incluyendo por completo.


Las opciones 1 y 2, ambos parecen muy bien el trabajo hasta el momento. Cualquiera de estas opciones puede ser "correcto", pero con asynciola evolución en los últimos años los ejemplos y sugerencias alrededor de la red no están actualizados o bien variar un poco. Así que mis preguntas son ...

Pregunta 1

¿Hay diferencias prácticas entre las opciones 1 y 2? Sé que run_until_completese extenderá hasta el futuro se ha completado, por lo que desde la opción 1 es un bucle en un orden específico supongo que podría comportarse de manera diferente si las tareas anteriores tardan más en realidad completa. He intentado mirar el código fuente asyncio de entender si asyncio.waitsólo lo hace efectivamente lo mismo con sus tareas / futuros bajo el capó, pero no era evidente.

Pregunta 2

Asumo que si una de las tareas es en medio de una operación de bloqueo de larga duración que en realidad no puede cancelar de inmediato? Tal vez eso sólo depende de si la operación subyacente o ser utilizados biblioteca elevará el CancelledError de inmediato o no? Tal vez eso no debería ocurrir nunca con librerías diseñadas para asyncio?

Desde que estoy tratando de implementar una función de tiempo de espera aquí estoy un poco sensible a esto. Si es posible, estas cosas podrían tomar mucho tiempo para cancelar lo consideraría llamar cancely no esperar a que ocurra realmente, o la creación de un corto tiempo de espera para esperar a los Cancela a fin.

Pregunta 3

¿Es posible loop.run_until_complete(o en realidad, la llamada subyacente a async.wait) devuelve los valores en unfinishedpor una razón que no sea un tiempo de espera? Si es así me gustaría, obviamente, tengo que ajustar mi lógica un poco, pero a partir de los documentos que parece que eso no es posible.

user4815162342:

¿Hay diferencias prácticas entre las opciones 1 y 2?

Nº Opción 2 se ve mejor y podría ser marginalmente más eficiente, pero su efecto neto es el mismo.

Sé que run_until_completese extenderá hasta el futuro se ha completado, por lo que desde la opción 1 es un bucle en un orden específico supongo que podría comportarse de manera diferente si las tareas anteriores tardan más en realidad completa.

Parece de esa manera al principio, pero en realidad no es el caso porque loop.run_until_completeejecuta todas las tareas presentadas al bucle, no sólo el que pasa como argumento. Simplemente se detiene una vez que se complete el awaitable previstos - que es lo que "correr hasta que se completa" se refiere a. Un bucle llamando a run_until_completecargo de las tareas ya programadas es como el siguiente código asíncrono:

ts = [asyncio.create_task(asyncio.sleep(i)) for i in range(1, 11)]
# takes 10s, not 55s
for t in ts:
    await t

que a su vez semánticamente equivalente al siguiente código de roscado:

ts = []
for i in range(1, 11):
    t = threading.Thread(target=time.sleep, args=(i,))
    t.start()
    ts.append(t)
# takes 10s, not 55s
for t in ts:
    t.join()

En otras palabras, await ty run_until_complete(t)el bloque hasta que tse ha completado, pero permitir que todo lo demás - como tareas programadas anteriormente utilizando asyncio.create_task()para funcionar durante ese tiempo también. Por lo que el tiempo de ejecución total será igual al tiempo de ejecución de la tarea más larga, no de su suma. Por ejemplo, si la primera tarea pasa a llevar mucho tiempo, todos los demás se han terminado en el ínterin, y sus aguarda no dormir en absoluto.

Todo esto sólo se aplica a la espera de las tareas que se han programado previamente. Si intenta aplicar eso a corrutinas, no va a funcionar:

# runs for 55s, as expected
for i in range(1, 11):
    await asyncio.sleep(i)

# also 55s
for i in range(1, 11):
   t = threading.Thread(target=time.sleep, args=(i,))
   t.start()
   t.join()

Esto es a menudo un punto de fricción para asyncio principiantes, que escriben código equivalente al último ejemplo asyncio y esperan que se ejecute en paralelo.

He intentado mirar el código fuente asyncio de entender si asyncio.waitsólo lo hace efectivamente lo mismo con sus tareas / futuros bajo el capó, pero no era evidente.

asyncio.wait es sólo una API de conveniencia que hace dos cosas:

  • convierte los argumentos de entrada a algo que los implementos Future. Para corrutinas que los medios que les plantea al bucle de eventos, como si con create_task, lo que les permite ejecutar de forma independiente. Si le dan tareas para empezar, como lo hace, se omite este paso.
  • usos add_done_callbackque se le notifique cuando el futuro se hacen, momento en el que vuelve a su persona que llama.

Así que sí, que hace las mismas cosas, pero con una implementación diferente porque es compatible con muchas más características.

Asumo que si una de las tareas es en medio de una operación de bloqueo de larga duración que en realidad no puede cancelar de inmediato?

En asyncio no debería haber "bloquear" las operaciones, sólo aquellos que suspender, y deben ser canceladas inmediatamente. La excepción a esto es el bloqueo de código pegado en asyncio con run_in_executor, donde la operación subyacente no se cancelará en absoluto, pero la co-rutina asyncio será inmediatamente obtener la excepción.

Tal vez eso sólo depende de si la operación subyacente o ser utilizados biblioteca elevará el CancelledError de inmediato o no?

La biblioteca no recaudar CancelledError , se recibe en el punto donde ocurrió lo esperan a suspender antes de producirse la cancelación. Para la biblioteca el efecto de la cancelación se await ...interrumpe su estrategia de esperar e inmediatamente levantar CancelledError. A no ser atrapado, la excepción se propagará a través de la función y awaitllama a todo el camino a la co-rutina de nivel superior, cuya elevar CancelledErrormarcas de toda la tarea como cancelada. Buen comportamiento asyncio código hará exactamente eso, posiblemente usando finallypara liberar recursos a nivel de sistema operativo que poseen. Cuando CancelledErrorse captura, el código puede optar por no volver a subir, en cuyo caso se ignora la cancelación efectiva.

¿Es posible loop.run_until_complete (o en realidad, la llamada subyacente a async.wait) devuelve los valores en inacabada por una razón que no sea un tiempo de espera?

Si está utilizando return_when=asyncio.ALL_COMPLETE(por defecto), que no debería ser posible. Es bastante posible con return_when=FIRST_COMPLETED, entonces obviamente es posible independientemente de tiempo de espera.

Supongo que te gusta

Origin http://43.154.161.224:23101/article/api/json?id=285251&siteId=1
Recomendado
Clasificación