有没有办法从 Swift 3 中的异步闭包中抛出错误?

Is there a way to throw errors from asynchronous closures in Swift 3?

我正在使用 DispatchQueue 异步执行测试中的一些函数,如下所示:

let queue: DispatchQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated)
let group: DispatchGroup = DispatchGroup()

func execute(argument: someArg) throws {
  group.enter()
  queue.async {
    do {
      // Do stuff here 
      group.leave()
    } catch {
       Log.info(“Something went wrong")
    }
  }
  group.wait()
}

有时 do 块中的代码可能会抛出错误,我必须在稍后捕获这些错误。由于我正在开发一个测试,如果 do 块中的代码抛出错误,我希望它失败。 有没有办法抛出错误,而不在 queue.async 调用中捕获它?

重构您的代码以使用 queue.sync,然后从那里抛出您的错误。 (因为你的 execute 函数实际上是同步的,考虑到最后一行的 group.wait() 调用,它应该无关紧要。)

例如,使用此方法来自 DispatchQueue:

func sync<T>(execute work: () throws -> T) rethrows -> T

顺便说一下,离开一个DispatchGroup的一个好成语是:

defer { group.leave() }

作为 sync/async 块的第一行 ,这保证您不会在发生错误时意外死锁。

你不能抛出一个错误,但是你可以return一个错误:

首先,您还需要使调用函数异步:

func execute(argument: someArg, completion: @escaping (Value?, Error?)->()) {
  queue.async {
    do {
      // compute value here:
      ...
      completion(value, nil)
    } catch {
       completion(nil, error)
    }
  }
}

完成处理程序接受一个参数,我们可以说它是一个包含值或错误的 "Result"。在这里,我们有一个元组 (Value?, Error?),其中 Value 是由任务计算的类型。但是,您可以为此利用更方便的 Swift 枚举,例如Result<T>Try<T>(您可能需要在网上搜索)。

然后,你使用它如下:

execute(argument: "Some string") { value, error in
    guard error == nil else {
        // handle error case
    }
    guard let value = value else {
        fatalError("value is nil") // should never happen!
    }
    // do something with the value
    ...
}

一些可能有用的规则:

  1. 如果一个函数内部调用了一个异步函数,那么它必然也变成了一个异步函数。 *)

  2. 一个异步函数应该有一个完成处理程序(否则,它是某种"fire and forget")。

  3. 无论如何都必须调用完成处理程序。

  4. 必须异步调用完成处理程序(相对于调用者)

  5. 应该在私有执行上下文(也称为调度队列)上调用完成处理程序,除非该函数具有指定在何处执行完成处理程序的参数。永远不要使用主线程或主调度队列 - 除非你在文档中明确说明这一事实或者你故意想要冒死锁的风险。

*) 您可以使用阻塞调用线程的信号量强制它同步。但这是低效的,而且很少需要。

嗯,您可能会得出结论,这看起来有些麻烦。幸运的是,这里有帮助——您可能会寻找 FuturePromise,它们可以很好地包装它并使代码更简洁、更易于理解。

注意:在单元测试中,您将使用期望来处理异步调用(请参阅 XCTest 框架)。