Ejecutores
**********

**Código fuente:** Lib/asyncio/runners.py

Esta sección muestra las primitivas asyncio de alto nivel para
ejecutar código asyncio.

Están construidos sobre un event loop con el objetivo de simplificar
el uso de código async para escenarios comunes de alta difusión.

* Ejecutando un programa asyncio

* Administrador de contexto del ejecutor

* Manejando interrupciones de teclado


Ejecutando un programa asyncio
==============================

asyncio.run(coro, *, debug=None)

   Ejecutar el *coroutine* *coro* y retornar el resultado.

   Esta función ejecuta la co-rutina pasada, teniendo cuidado de
   manejar el bucle de eventos asyncio, *terminando los generadores
   asíncronos* y cerrando el pool de hilos.

   Esta función no puede ser llamada cuando otro bucle de eventos
   asyncio está corriendo en el mismo hilo.

   Si *debug* es "True", el bucle de eventos se ejecutará en modo
   debug. "False" deshabilita el modo debug de manera explícita.
   "None" se usa para respetar la configuración global Modo
   depuración.

   Esta función siempre crea un nuevo bucle de eventos y lo cierra al
   final. Debería ser usado como el punto de entrada principal para
   programas asyncio e idealmente, llamado una sola vez.

   Ejemplo:

      async def main():
          await asyncio.sleep(1)
          print('hello')

      asyncio.run(main())

   Nuevo en la versión 3.7.

   Distinto en la versión 3.9: Actualizado para usar
   "loop.shutdown_default_executor()".

   Distinto en la versión 3.10: *debug* es "None" por defecto para
   respetar la configuración global del modo debug.


Administrador de contexto del ejecutor
======================================

class asyncio.Runner(*, debug=None, loop_factory=None)

   Un administrador de contexto que simplifica *multiples* llamadas
   asíncronas en el mismo contexto.

   A veces varias funciones asíncronas de alto nivel deberían ser
   llamadas en el mismo bucle de eventos y "contextvars.Context".

   Si *debug* es "True", el bucle de eventos se ejecutará en modo
   debug. "False" deshabilita el modo debug de manera explícita.
   "None" se usa para respetar la configuración global Modo
   depuración.

   *loop_factory* puede ser usado para redefinir la creación de
   bucles. Es responsabilidad de la *loop_factory* configurar el bucle
   creado como el bucle actual. Por defecto "asyncio.new_event_loop()"
   es usado y configura el nuevo bucle de eventos como el actual con
   "asyncio.set_event_loop()" si *loop_factory* es "None".

   Básicamente, el ejemplo "asyncio.run()" puede ser re-escrito usando
   el ejecutor:

      async def main():
          await asyncio.sleep(1)
          print('hello')

      with asyncio.Runner() as runner:
          runner.run(main())

   Nuevo en la versión 3.11.

   run(coro, *, context=None)

      Ejecuta una *co-rutina* *coro* en el bucle embebido.

      Retorna el resultado de la co-rutina o lanza excepción de dicha
      co-rutina.

      Un argumento opcional del *contexto* que consiste en una palabra
      clave permite especificar un "contextvars.Context" personalizado
      donde correr la *coro* . El contexto por defecto del ejecutor es
      usado si el modo debug es "None".

      Esta función no puede ser llamada cuando otro bucle de eventos
      asyncio está corriendo en el mismo hilo.

   close()

      Cierra el ejecutor.

      Termina los generadores asíncronos, apaga el ejecutor por
      defecto, cierra el bucle de eventos y libera el
      "contextvars.Context" embebido.

   get_loop()

      Retorna el bucle de eventos asociado a la instancia del
      ejecutor.

   Nota:

     "Runner" usa una estrategia de inicialización perezosa, su
     constructor no inicializa las estructuras de bajo nivel
     subyacentes.El *bucle* y el *contexto* embebidos son creados al
     entrar al cuerpo "with" o en la primera llamada a "run()" o a
     "get_loop()".


Manejando interrupciones de teclado
===================================

Nuevo en la versión 3.11.

Cuando la excepción "signal.SIGINT" es lanzada por "Ctrl-C", la
excepción "KeyboardInterrupt" es lanzada en el hilo principal por
defecto. Sin embargo, esto no siempre funciona con "asyncio" porque
puede interrumpir llamadas internas a asyncio e impedir la salida del
programa.

Para mitigar este problema, "asyncio" maneja "signal.SIGINT" de la
siguiente forma:

1. "asyncio.Runner.run()" instala un administrador "signal.SIGINT"
   personalizado antes que cualquier código de usuario sea ejecutado y
   lo remueve a la salida de la función.

2. La "Runner" crea la tarea principal que será pasada a la co-rutina
   para su ejecución.

3. When "signal.SIGINT" is raised by "Ctrl-C", the custom signal
   handler cancels the main task by calling "asyncio.Task.cancel()"
   which raises "asyncio.CancelledError" inside the main task.  This
   causes the Python stack to unwind, "try/except" and "try/finally"
   blocks can be used for resource cleanup.  After the main task is
   cancelled, "asyncio.Runner.run()" raises "KeyboardInterrupt".

4. Un usuario podría escribir un bucle cerrado que no puede ser
   interrumpido por "asyncio.Task.cancel()", en cuyo caso la segunda
   llamada a "Ctrl-C" lanza inmediatamente "KeyboardInterrupt" sin
   cancelar la tarea principal.
