後端工坊 2026 年 9 月 16 日

2026-09-16 — Swift 6.4:defer 清理不再被取消打斷

primary=https://www.swift.org/blog/swift-6.4-released/ primary=https://github.com/swiftlang/swift-evolution/blob/main/proposals/0493-defer-async.md primary=https://github.com/swiftlang/swift-evolution/blob/main/proposals/0504-task-cancellation-shields.md

Swift 6.4:defer 清理不再被取消打斷

Swift.org · 2026-09-15

Swift 6.4 把非同步清理程式碼的兩個老毛病一次補上:defer 區塊終於可以直接 await,而新的 withTaskCancellationShield 讓這段清理不會被取消訊號腰斬。這兩個提案 SE-0493SE-0504 隨 Swift 6.4 於 2026 年 9 月 15 日一起發布,正式取代過去只能用 fire-and-forget 的 Taskguard !Task.isCancelled 手動繞過的寫法。

背景

在 Swift 6.4 之前,defer 區塊裡完全不能呼叫非同步函式:只要在裡面寫 await,編譯器就會丟出 'async' call cannot occur in a defer body 錯誤。這逼得工程師把清理邏輯搬出 defer,最常見的繞路是在 defer 裡開一個 fire-and-forget 的 Task 去做非同步收尾,但這個 Task 何時執行、會不會在函式回傳前跑完,完全沒有保證。

更麻煩的是取消(cancellation)。許多清理函式會用 guard !Task.isCancelledtry Task.checkCancellation() 當防呆,一旦外部任務被取消,這些檢查反而讓清理本身提早跳出——本該執行的資源釋放因為「已取消」被連帶跳過。SE-0504 的提案文件直接點名這個場景:關閉連線、釋放檔案 handle 這類收尾工作,恰恰最需要在取消之後仍然跑完。

核心改動

SE-0493 讓 defer 區塊在外層是 async 時可以直接寫 await,函式離開作用域時,這段非同步清理會被隱式 await,保證在函式真正回傳前執行完畢;defer 主體沿用外層的 isolation,不會另外引入新的暫停點。SE-0504 則新增 withTaskCancellationShield,讓包在裡面的程式碼在執行期間觀察不到目前任務已被取消——它不是「忽略」取消,取消狀態依然存在,只是這段程式碼看不見,離開 shield 之後外部照樣看得到已取消的狀態。

// Before:只能用 fire-and-forget Task 繞過編譯錯誤,且不保證跑完
func f() async {
  await setUp()
  defer {
    Task { await performAsyncTeardown() }
  }
  try doSomething()
}

// After:defer 直接 await,並用 shield 保護不被取消打斷
func f() async {
  await setUp()
  defer {
    await withTaskCancellationShield {
      await performAsyncTeardown()
    }
  }
  try doSomething()
}

兩份提案文件都提到 SE-0504 與 SE-0493 的非同步 defer「相輔相成」,因為最常見的用法就是把 withTaskCancellationShield 包在 defer 的 await 呼叫外層。withTaskCancellationShield 提供同步與非同步兩個版本,兩個版本都以泛型 Failure 轉送內部拋出的錯誤型別:同步版簽名是 (() throws(Failure) -> Value) throws(Failure) -> Value,非同步版則額外標成 nonisolated(nonsending)

影響範圍

受影響最直接的是伺服器端 Swift 專案裡的資源清理程式碼:關閉資料庫連線、釋放檔案 handle、優雅關閉 socket、送出最後一筆 metrics,這些以前寫在 defer 裡會被編譯器擋下、只能丟給 detached Task 的邏輯,現在可以拿掉繞路寫法,直接寫成一段線性的 await

  • 清理函式裡用 Task.isCancelledTask.checkCancellation() 防呆的程式碼,要重新檢查這些檢查是不是連清理本身也一起擋下,必要時包進 withTaskCancellationShield
  • 目前用 fire-and-forget Task 繞過 defer 限制的既有程式碼,升級到 Swift 6.4 工具鏈後可以改回直接 await,順便拿掉「不保證執行完」的風險。
  • Vapor、SwiftNIO 這類長駐連線的伺服器框架,其請求與連線生命週期的收尾邏輯是最典型的受益對象。

兩個提案都不影響 ABI,也不會改變既有程式碼的行為——沒有寫 awaitdefer 區塊完全不受影響,純屬新增能力。Swift 6.4 同一版還有 module selectors(SE-0491)等其他語言提案,但真正動到既有清理程式碼寫法的,就是這兩個並肩而來的並發修正。

原始來源:Swift.org 官方公告SE-0493 提案SE-0504 提案


End of article
0
Would love your thoughts, please comment.x
()
x