【问题标题】:Is there a way to throw errors from asynchronous closures in Swift 3?有没有办法从 Swift 3 中的异步闭包中抛出错误?
【发布时间】:2017-04-10 20:14:32
【问题描述】:

我正在使用 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 调用中捕获它?

【问题讨论】:

    标签: swift asynchronous swift3 grand-central-dispatch


    【解决方案1】:

    不能抛出错误,但可以返回错误:

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

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

    完成处理程序接受一个参数,我们可以说它是一个包含值或错误的“结果”。在这里,我们有一个元组(Value?, Error?),其中Value 是由任务计算的类型。但是,您可以为此利用更方便的 Swift Enum,例如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. 异步函数应该有一个完成处理程序(否则,它是某种“即发即弃”)。

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

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

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

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

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

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

    【讨论】:

      【解决方案2】:

      重构您的代码以使用queue.sync,然后从那里抛出您的错误。 (由于您的 execute 函数实际上是同步的,考虑到最后一行的 group.wait() 调用,这并不重要。)

      例如,使用DispatchQueue中的这个方法:

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

      顺便说一句,离开DispatchGroup的一个很好的习惯用法是:

      defer { group.leave() }
      

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

      【讨论】:

      • 什么是group? (你在defer 块中使用它)
      • @VolodymyrKulyk group 在上面的问题中定义。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-22
      • 1970-01-01
      • 2016-04-29
      • 1970-01-01
      • 2010-12-08
      • 1970-01-01
      相关资源
      最近更新 更多