我有一个执行异步任务的函数。有时该任务会失败并引发错误。我无法从调用函数中捕获该错误。下面的操场抓住了我遇到的麻烦的本质。
import UIKit
Task {
var newNum: Double = 99.9
do {
newNum = try await getMyNumber()
print("newNum within do: \(newNum)")
} catch MyErrors.BeingStupid { //never gets caught
print("caught being stupid")
} catch MyErrors.JustBecause { //does get caught if throw is uncommented
print("caught just because")
}
print("newNum outside of do \(newNum)")
}
print("done with main")
func getMyNumber() async throws -> Double {
let retNum:Double = 0
Task{
sleep(5)
let myNum: Double = Double.random(in: (0...10))
if myNum > 9 {
print("greater than 9")
} else {
print("less than 9 -- should throw")
throw MyErrors.BeingStupid // error doesn't get thrown? HOW DO I CATCH THIS?
}
}
// throw MyErrors.JustBecause // this *does* get caught if uncommented
return retNum //function always returns
}
enum MyErrors: Error {
case BeingStupid, JustBecause
}
如何捕获调用函数中注释为“HOW DO I CATCH THIS”的行引发的错误?
Task
用于非结构化并发。如果目的是模拟异步任务,我建议保持结构化并发。因此,使用 Task.sleep(nanoseconds:)
代替 sleep()
并消除 Task
中的 getMyNumber
:
func getMyNumber() async throws -> Double {
try await Task.sleep(for: .seconds(5)) // better simulation of some asynchronous process
let myNum = Double.random(in: 0...10)
guard myNum > 9 else {
print("less than or equal to 9 -- should throw")
throw MyErrors.beingStupid
}
print("greater than 9")
return myNum
}
如果保持结构化并发,抛出的错误很容易被捕获。
有关结构化和非结构化并发之间差异的更多信息,请参阅 Swift 编程指南:并发 或 WWDC 2021 视频 探索 Swift 中的结构化并发
上面说明了标准的结构化并发模式。如果您真的必须使用非结构化并发,您可以
try await
由value
返回的Task
,从而重新抛出由Task
抛出的任何错误,例如:
func getMyNumber() async throws -> Double {
let task = Task.detached {
let start = ContinuousClock().now
while start.duration(to: .now) < .seconds(5) {
Thread.sleep(forTimeInterval: 0.2) // really bad idea ... never `sleep` (except `Task.sleep`, which is non-blocking) ... especially never sleep on the main actor, which is why I used `Task.detached`
try Task.checkCancellation() // but periodically check to see if task was canceled
await Task.yield() // and if doing something slow and synchronous, periodically yield to Swift concurrency
}
let myNum = Double.random(in: 0...10)
guard myNum > 9 else {
print("less than or equal to 9 -- should throw")
throw MyErrors.beingStupid
}
print("greater than 9")
return myNum
}
return try await withTaskCancellationHandler {
try await task.value
} onCancel: {
task.cancel()
}
}
注意,因为我们正在运行一些缓慢且同步的东西,所以我们希望它在后台线程上运行,因此使用
Task.detached
。但是,当使用非结构化并发时,我们希望正确处理取消,即:
Task
包裹在 withTaskCancellationHandler
中,因为在使用非结构化并发时我们必须手动处理取消;和yield
到 Swift 并发系统。为了完整起见,我仅包含这个非结构化并发示例。如果您
try await
由 value
返回的 Task
,错误将被正确传播。
综上所述,如果可以的话,最好保持结构化并发。这样,您不仅可以享受更简洁的实现,还可以自动传播取消等。