# Runners (執行器)

原始碼：Lib/asyncio/runners.py

這個章節概述用於執行 asyncio 程式碼的高階 asyncio 原始物件。

他們是基於一個事件迴圈，目的是為了簡化常見且廣泛運用場景的非同步程式碼。

## 運行一個 asyncio 程式

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

  執行協程 (coroutine) coro 並回傳結果。

  這個函式負責運行被傳入的協程、管理 asyncio 的事件迴圈、終結非同步產生器，以及關閉執行器。

  當另一個非同步事件迴圈在同一執行緒中執行時，無法呼叫此函式。

  如果 debug 為 `True`，事件迴圈會以除錯模式執行。`False` 則會關閉除錯模式。`None` 則會優先使用除錯模式的全域設定。

  如果 loop_factory 不為 `None`，它會被用於建立一個新的事件迴圈；否則會改用 `asyncio.new_event_loop()`。迴圈會在最後關閉。這個函式應該要作為asyncio 程式的主要進入點，且理想上僅會被呼叫一次。推薦使用 loop_factory 來設定事件迴圈時而不是使用 policies（政策）。

  The executor is given a timeout duration of 5 minutes to shutdown. If the executor hasn't finished within that duration, a warning is emitted and the executor is closed.

  範例：

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

      asyncio.run(main())

  在 3.7 版被加入.

  在 3.9 版的變更: Updated to use `loop.shutdown_default_executor()`.

  在 3.10 版的變更: debug is `None` by default to respect the global debug mode settings.

  在 3.12 版的變更: 新增 loop_factory 參數。

## Runner context manager

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

  A context manager that simplifies multiple async function calls in the same context.

  Sometimes several top-level async functions should be called in the same event loop and `contextvars.Context`.

  如果 debug 為 `True`，事件迴圈會以除錯模式執行。`False` 則會關閉除錯模式。`None` 則會優先使用除錯模式的全域設定。

  loop_factory could be used for overriding the loop creation. It is the responsibility of the loop_factory to set the created loop as the current one. By default `asyncio.new_event_loop()` is used and set as current event loop with `asyncio.set_event_loop()` if loop_factory is `None`.

  Basically, `asyncio.run()` example can be rewritten with the runner usage:

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

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

  在 3.11 版被加入.

  run(coro, *, context=None)

    Run a coroutine coro in the embedded loop.

    Return the coroutine's result or raise its exception.

    An optional keyword-only context argument allows specifying a custom `contextvars.Context` for the coro to run in. The runner's default context is used if `None`.

    當另一個非同步事件迴圈在同一執行緒中執行時，無法呼叫此函式。

  close()

    Close the runner.

    Finalize asynchronous generators, shutdown default executor, close the event loop and release embedded `contextvars.Context`.

  get_loop()

    Return the event loop associated with the runner instance.

  備註

  `Runner` uses the lazy initialization strategy, its constructor doesn't initialize underlying low-level structures.

  Embedded loop and context are created at the `with` body entering or the first call of `run()` or `get_loop()`.

## Handling Keyboard Interruption

在 3.11 版被加入.

When `signal.SIGINT` is raised by `Ctrl`-`C`, `KeyboardInterrupt` exception is raised in the main thread by default. However this doesn't work with `asyncio` because it can interrupt asyncio internals and can hang the program from exiting.

To mitigate this issue, `asyncio` handles `signal.SIGINT` as follows:

1. `asyncio.Runner.run()` installs a custom `signal.SIGINT` handler before any user code is executed and removes it when exiting from the function.

2. The `Runner` creates the main task for the passed coroutine for its execution.

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. A user could write a tight loop which cannot be interrupted by `asyncio.Task.cancel()`, in which case the second following `Ctrl`-`C` immediately raises the `KeyboardInterrupt` without cancelling the main task.