Swift 抛出的“async let”错误取决于任务的执行顺序

Nun*_*ves 7 async-await swift

我试图理解async let错误处理,但在我看来这没有多大意义。看来,如果我有两个并行请求,第一个抛出异常的请求不会取消另一个请求。事实上,这仅取决于它们的制作顺序。

我的测试设置:

struct Person {}
struct Animal {}
enum ApiError: Error { case person, animal }

class Requester {

    init() {}

    func getPeople(waitingFor waitTime: UInt64, throwError: Bool) async throws -> [Person] {
        try await waitFor(waitTime)
        if throwError { throw ApiError.person }
        return []
    }

    func getAnimals(waitingFor waitTime: UInt64, throwError: Bool) async throws -> [Animal] {
        try await waitFor(waitTime)
        if throwError { throw ApiError.animal }
        return []
    }

    func waitFor(_ seconds: UInt64) async throws {
        do {
            try await Task.sleep(nanoseconds: NSEC_PER_SEC * seconds)
        } catch {
            print("Error waiting", error)
            throw error
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

练习。


class ViewController: UIViewController {

    let requester = Requester()

    override func viewDidLoad() {
        super.viewDidLoad()
        Task {
            async let animals = self.requester.getAnimals(waitingFor: 1, throwError: true)
            async let people = self.requester.getPeople(waitingFor: 2, throwError: true)

            let start = Date()
            do {
//                let (_, _) = try await (people, animals)
                let (_, _) = try await (animals, people)
                print("No error")
            } catch {
                print("error: ", error)
            }
            print(Date().timeIntervalSince(start))
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

为简单起见,从现在开始,我将跳过相关的代码行和输出。

场景一:

async let animals = self.requester.getAnimals(waitingFor: 1, throwError: true)
async let people = self.requester.getPeople(waitingFor: 2, throwError: true)
let (_, _) = try await (animals, people)
Run Code Online (Sandbox Code Playgroud)

结果是:

错误:动物 1.103397011756897 等待 CancellationError() 时出错

这按预期工作。较慢的请求,需要 2 秒,但在 1 秒后被取消(当最快的请求抛出时)

场景2:

async let animals = self.requester.getAnimals(waitingFor: 2, throwError: true)
async let people = self.requester.getPeople(waitingFor: 1, throwError: true)
let (_, _) = try await (animals, people)
Run Code Online (Sandbox Code Playgroud)

结果是:

错误:动物2.2001450061798096

现在这对我来说并不是期望的。people 请求需要 1 秒才能抛出错误,而我们仍然等待 2 秒,错误是animal。我的预期是这应该是 1 秒并且人为错误。

场景3:

async let animals = self.requester.getAnimals(waitingFor: 2, throwError: true)
async let people = self.requester.getPeople(waitingFor: 1, throwError: true)
let (_, _) = try await (people, animals)
Run Code Online (Sandbox Code Playgroud)

结果是:

错误:人 1.0017549991607666 等待 CancellationError() 时出错

现在这是预期的。这里的区别在于我交换了请求的顺序,但更改为try await (people, animals).

哪个方法先抛出并不重要,我们总是会得到第一个错误,并且花费的时间也取决于该顺序。

这种行为是预期的/正常的吗?我是否看到了什么错误,或者我是否测试了错误?

我很惊讶人们并没有更多地谈论这一点。我只是在开发者论坛中发现了另一个类似的问题。

请帮忙。:)

cor*_*ora 4

来自https://github.com/apple/swift-evolution/blob/main/proposals/0317-async-let.md

async let (l, r) = {
  return await (left(), right())
  // -> 
  // return (await left(), await right())
}
Run Code Online (Sandbox Code Playgroud)

这意味着 async let 的整个初始化器是一个任务,如果在其中进行多个异步函数调用,它们将被一一执行。

这是一种更加结构化的方法,其行为有意义。

struct ContentView: View {
    var body: some View {
        Text("Hello, world!")
            .padding()
            .task {
                let requester = Requester()
                let start = Date()
                
                await withThrowingTaskGroup(of: Void.self) { group in
                    let animalTask = Task {
                        try await requester.getAnimals(waitingFor: 1, throwError: true)
                    }
                    group.addTask { animalTask }
                    group.addTask {
                        try await requester.getPeople(waitingFor: 2, throwError: true)
                    }
                    
                    do {
                        for try await _ in group {
                            
                        }
                        group.cancelAll()
                    } catch ApiError.animal {
                        group.cancelAll()
                        print("animal threw")
                    } catch ApiError.person {
                        group.cancelAll()
                        print("person threw")
                    } catch {
                        print("someone else")
                    }
                }
                
                print(Date().timeIntervalSince(start))
            }
    }
}
Run Code Online (Sandbox Code Playgroud)

这个想法是将每个任务添加到一个投掷组,然后循环执行每个任务。