Runner
******

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

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

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

* 執行 asyncio 程式

* Runner 情境管理器

* 處理鍵盤中斷


執行 asyncio 程式
=================

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

   在 asyncio 事件迴圈中執行 *coro* 並回傳結果。

   該引數可以是任何可等待物件 (awaitable object)。

   這個函式負責執行傳入的可等待物件、管理 asyncio 的事件迴圈、*最終化
   非同步產生器*以及關閉執行器。

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

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

   如果 *loop_factory* 不為 "None"，它會被用於建立一個新的事件迴圈；否
   則會改用 "asyncio.new_event_loop()"。迴圈會在最後關閉。這個函式應該
   要作為asyncio 程式的主要進入點，且理想上僅會被呼叫一次。推薦使用
   *loop_factory* 來設定事件迴圈時而不是使用 policies（政策）。傳遞
   "asyncio.EventLoop" 可以讓 asyncio 在沒有政策系統的情況下運行。

   執行器的關閉逾時期限為 5 分鐘。如果執行器未在這段時間內完成，系統會
   發出警告並關閉執行器。

   範例：

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

      asyncio.run(main())

   在 3.7 版被加入.

   在 3.9 版的變更: 更新為使用 "loop.shutdown_default_executor()"。

   在 3.10 版的變更: *debug* 預設為 "None"，以遵循全域偵錯模式設定。

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

   在 3.14 版的變更: *coro* 可以是任何可等待物件。

   備註:

     "asyncio" 的政策系統已被棄用，並將於 Python 3.16 移除；此後必須明
     確指定 *loop_factory* 才能設定事件迴圈。


Runner 情境管理器
=================

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

   此情境管理器可簡化在相同情境中對*多個*非同步函式的呼叫。

   有時需要在相同的 事件迴圈 和 "contextvars.Context" 中呼叫數個頂層非
   同步函式。

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

   *loop_factory* 可用來覆寫事件迴圈的建立方式。*loop_factory* 必須負
   責將建立的迴圈設定為目前的事件迴圈。若 *loop_factory* 為 "None"，預
   設會使用 "asyncio.new_event_loop()"，並以
   "asyncio.set_event_loop()" 將其設定為目前的事件迴圈。

   基本上，"asyncio.run()" 的範例可以改寫為使用 Runner：

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

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

   在 3.11 版被加入.

   run(coro, *, context=None)

      在內嵌的事件迴圈中執行 *coro*。

      該引數可以是任何可等待物件 (awaitable object)。

      如果引數是協程，會將其包裝在 Task 中。

      可選的僅限關鍵字引數 *context*，可用來指定程式碼執行時所使用的自
      訂 "contextvars.Context"。如果 context 為 "None"，則使用 Runner
      的預設情境。

      回傳可等待物件的結果，或引發例外。

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

      在 3.14 版的變更: *coro* 可以是任何可等待物件。

   close()

      關閉 Runner。

      最終化非同步產生器、關閉預設執行器與事件迴圈，並釋放內嵌的
      "contextvars.Context"。

   get_loop()

      回傳與 Runner 實例關聯的事件迴圈。

   備註:

     "Runner" 採用延遲初始化策略，其建構函式不會初始化底層的低階結構。
     內嵌的 *loop* 和 *context* 會在進入 "with" 陳述式主體，或第一次呼
     叫 "run()" 或 "get_loop()" 時建立。


處理鍵盤中斷
============

在 3.11 版被加入.

當 "signal.SIGINT" 由 "Ctrl"-"C" 引發時，預設會在主執行緒中引發
"KeyboardInterrupt" 例外。然而，此方式不適用於 "asyncio"，因為這可能會
中斷 asyncio 內部運作，並造成程式無法結束。

為了緩解此問題，"asyncio" 會依下列方式處理 "signal.SIGINT"：

1. "asyncio.Runner.run()" 會在執行任何使用者程式碼之前安裝自訂的
   "signal.SIGINT" 處理程式，並在函式結束時將其移除。

2. "Runner" 會為傳入的協程建立主 Task，以執行該協程。

3. 當 "signal.SIGINT" 由 "Ctrl"-"C" 引發時，自訂訊號處理程式會呼叫
   "asyncio.Task.cancel()" 來取消主 Task，而此方法會在主 Task 內引發
   "asyncio.CancelledError"。這會使 Python 堆疊展開，並可使用
   "try/except" 與 "try/finally" 區塊來清理資源。主 Task 取消後，
   "asyncio.Runner.run()" 會引發 "KeyboardInterrupt"。

4. 使用者可能會撰寫一個無法由 "asyncio.Task.cancel()" 中斷的緊密迴圈；
   在此情況下，再次按下 "Ctrl"-"C" 會立即引發 "KeyboardInterrupt"，而
   不會取消主 Task。
