Runners (執行器)
****************

**原始碼：**Lib/asyncio/runners.py

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

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

* 運行一個 asyncio 程式

* Runner context manager

* Handling Keyboard Interruption


運行一個 asyncio 程式
=====================

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

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

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

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

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

   If *loop_factory* is not "None", it is used to create a new event
   loop; otherwise "asyncio.new_event_loop()" is used. The loop is
   closed at the end. This function should be used as a main entry
   point for asyncio programs, and should ideally only be called once.
   It is recommended to use *loop_factory* to configure the event loop
   instead of policies. Passing "asyncio.EventLoop" allows running
   asyncio without the policy system.

   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.
