如何正确抛出和捕获来自 swift 异步函数的错误

问题描述 投票:0回答:1

我有一个执行异步任务的函数。有时该任务会失败并引发错误。我无法从调用函数中捕获该错误。下面的操场抓住了我遇到的麻烦的本质。

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”的行引发的错误?

swift asynchronous async-await throw
1个回答
2
投票

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
,错误将被正确传播。

综上所述,如果可以的话,最好保持结构化并发。这样,您不仅可以享受更简洁的实现,还可以自动传播取消等。

© www.soinside.com 2019 - 2024. All rights reserved.