科技 · 實用指南
Python 出現 cannot schedule new futures after shutdown?先證明哪個生命週期先結束
先區分執行器實例已關閉、直譯器退出與 asyncio 預設執行器收尾,再決定重試、替換或調整容量。

RuntimeError: cannot schedule new futures after shutdown 不是要求增加工作執行緒。它表示提交者跨過生命週期邊界:某個 Executor 已關閉、asyncio 預設執行器已收尾,或直譯器已開始退出。精確後綴與完整 submit 呼叫堆疊會指出真正的所有者。
增加重試前先保存證據。安全修復讓每一代執行器只有一位所有者:先停止新生產者,再明確等待或取消已接受工作,最後只呼叫一次 shutdown。若程序退出,必要控制執行緒不能只靠 daemon;若 asyncio.run 擁有迴圈,也應讓它管理預設執行器。
1. 分類精確的關閉訊號
保存完整回溯,記錄訊息是 after shutdown 還是 after interpreter shutdown,並確認呼叫來自 submit、map、排程器、函式庫助手或 run_in_executor。
在建立、每次接受提交、開始關閉、關閉完成及首次拒絕時記錄執行器世代、程序號、執行緒或任務名與單調時間。修復依賴公開所有權與順序契約。
2. 修復實例關閉後的重用
with 區塊退出時會關閉執行器。長期物件若保存該實例,第一次可成功,第二次就失敗。每次獨立執行可建立新執行器,或把 with 移到真正擁有完整重用生命週期的元件。
不要讓每個呼叫者捕捉 RuntimeError 後自行建立新池。所有者應以鎖或狀態機公布目前世代,進入 draining 後拒絕新工作,並在舊世代核清後才替換。
3. 修復直譯器退出與 daemon 競態
interpreter shutdown 表示程序生命週期正在結束。常見原因是 main 已返回,但 daemon 控制器、排程器、回呼或傳輸助手仍準備提交工作;主執行緒未處理例外也可能觸發。
使用明確停止訊號:停生產者、禁止新提交、等待控制執行緒、結算 futures、關閉執行器,最後才讓 main 返回。必要工作不能只有 daemon 所有者,atexit 也無法復活已收尾的執行器。
4. 尊重 asyncio 預設執行器所有權
run_in_executor(None, ...) 使用迴圈的預設執行器。shutdown_default_executor() 後再提交會報錯。使用 asyncio.run 時,runner 已管理生成器、預設執行器與迴圈關閉,不應重複收尾。
手動管理迴圈時,先停止會產生 executor 工作的回呼與服務,等待相關任務,再 await shutdown_default_executor(),最後關閉迴圈。跨執行緒回呼必須在邊界前拒絕或排空。
5. 以重啟與退出情境證明修復
至少測試連續兩次正常執行、排隊中關閉、生產者活躍時重啟、主執行緒失敗。每個已接受工作只能有一個終態,draining 後不得提交,執行緒數要回到基線。
先定義待處理工作要完成或取消,再明確斷言。驗收證據應顯示生產者停止早於 executor 關閉,且壓力測試沒有重複副作用、孤兒執行緒、吞掉例外或延遲提交。
