¿Cuándo usar y cuándo no usar Python 3.5 `await`?

Resuelto dalanmiller asked hace 9 años • 2 respuestas

Estoy entendiendo el flujo de uso asyncioen Python 3.5 pero no he visto una descripción de qué cosas debería awaithacer y qué no debería hacer o dónde sería insignificante. ¿Tengo que usar mi mejor criterio en términos de "esta es una operación IO y, por lo tanto, debería awaiteditarse"?

dalanmiller avatar Oct 27 '15 06:10 dalanmiller
Aceptado

De forma predeterminada, todo su código es sincrónico. Puede hacerlo asíncrono definiendo funciones async defy "llamando" a estas funciones con await. Una pregunta más correcta sería "¿Cuándo debo escribir código asincrónico en lugar de sincrónico?". La respuesta es "Cuando puedas beneficiarte de ello". En los casos en que trabaje con operaciones de E/S como señaló, generalmente se beneficiará:

# Synchronous way:
download(url1)  # takes 5 sec.
download(url2)  # takes 5 sec.
# Total time: 10 sec.

# Asynchronous way:
await asyncio.gather(
    async_download(url1),  # takes 5 sec. 
    async_download(url2)   # takes 5 sec.
)
# Total time: only 5 sec. (+ little overhead for using asyncio)

Por supuesto, si creó una función que usa código asincrónico, esta función también debería ser asincrónica (debe definirse como async def). Pero cualquier función asincrónica puede utilizar libremente código síncrono. No tiene sentido convertir código síncrono a asíncrono sin algún motivo:

# extract_links(url) should be async because it uses async func async_download() inside
async def extract_links(url):  

    # async_download() was created async to get benefit of I/O
    html = await async_download(url)  

    # parse() doesn't work with I/O, there's no sense to make it async
    links = parse(html)  

    return links

Una cosa muy importante es que cualquier operación sincrónica larga (> 50 ms, por ejemplo, es difícil decirlo exactamente) congelará todas sus operaciones asincrónicas durante ese tiempo:

async def extract_links(url):
    data = await download(url)
    links = parse(data)
    # if search_in_very_big_file() takes much time to process,
    # all your running async funcs (somewhere else in code) will be frozen
    # you need to avoid this situation
    links_found = search_in_very_big_file(links)

Puede evitar que llame a funciones síncronas de larga ejecución en procesos separados (y espere el resultado):

executor = ProcessPoolExecutor(2)

async def extract_links(url):
    data = await download(url)
    links = parse(data)
    # Now your main process can handle another async functions while separate process running    
    links_found = await loop.run_in_executor(executor, search_in_very_big_file, links)

Un ejemplo más: cuando necesitas usarlo requestsen asyncio. requests.getes solo una función síncrona de larga duración, a la que no debes llamar dentro del código asíncrono (nuevamente, para evitar la congelación). Pero tarda mucho debido a la E/S, no a causa de cálculos prolongados. En ese caso, puedes usar ThreadPoolExecutoren lugar de ProcessPoolExecutorpara evitar algunos gastos generales de multiprocesamiento:

executor = ThreadPoolExecutor(2)

async def download(url):
    response = await loop.run_in_executor(executor, requests.get, url)
    return response.text
Mikhail Gerasimov avatar Oct 28 '2015 19:10 Mikhail Gerasimov

No tienes mucha libertad. Si necesita llamar a una función, debe averiguar si se trata de una función habitual o de una corrutina. Debe utilizar la awaitpalabra clave si y sólo si la función que está llamando es una corrutina.

Si asynchay funciones involucradas, debería haber un "bucle de eventos" que orqueste estas asyncfunciones. Estrictamente hablando, no es necesario, puede ejecutar "manualmente" el asyncmétodo enviándole valores, pero probablemente no quiera hacerlo. El bucle de eventos realiza un seguimiento de las corrutinas que aún no han terminado y elige la siguiente para continuar ejecutándose. asyncioEl módulo proporciona una implementación de bucle de eventos, pero esta no es la única implementación posible.

Considere estas dos líneas de código:

x = get_x()
do_something_else()

y

x = await aget_x()
do_something_else()

La semántica es absolutamente la misma: llama a un método que produce algún valor, cuando el valor esté listo, asígnalo a la variable xy haz otra cosa. En ambos casos, la do_something_elsefunción se llamará solo después de que finalice la línea de código anterior. Ni siquiera significa que antes, después o durante la ejecución del aget_xmétodo asincrónico el control se cederá al bucle de eventos.

Aún así hay algunas diferencias:

  • el segundo fragmento solo puede aparecer dentro de otra asyncfunción
  • aget_xla función no es habitual, sino una rutina (que se declara con asyncuna palabra clave o se adorna como una rutina)
  • aget_xes capaz de "comunicarse" con el bucle de eventos: es decir, cederle algunos objetos. El bucle de eventos debería poder interpretar estos objetos como solicitudes para realizar algunas operaciones (por ejemplo, enviar una solicitud de red y esperar una respuesta, o simplemente suspender esta rutina por nunos segundos). La función habitual get_xno puede comunicarse con el bucle de eventos.
lesnik avatar Jan 05 '2018 18:01 lesnik