Deferred 构建状态

一个用于 Swift 5 及更高版本的无锁、异步 Result

Deferred<T, E: Error> 允许你将计算链接在一起。一个 Deferred 从一个不确定的、未解决的值开始。在稍后的某个时间点,它可能会变成已解决的。然后,只要该特定的 Deferred 实例存在,它的值就是不可变的。在 Deferred 变得已解决之前,依赖于它的计算可以使用无锁、线程安全的算法保存以供将来执行。这些计算的结果也由 Deferred 实例表示。在 Deferred 上下文中的任何点抛出的错误都会毫不费力地传播。

Deferred 最初是对 OCaml 的模块 Deferred 的近似。

let d = Deferred<Double, Never> { // holds a `Double`, cannot hold an `Error`
  usleep(50000) // or a long calculation
  return 1.0
}
print(d.value!) // 1.0, after 50 milliseconds

一个 Deferred 可以使用 notifymapflatMap 方法(以及其他方法)安排依赖于其结果的代码块以供将来执行。任意数量的此类代码块可以依赖于 Deferred 的结果。当抛出一个 Error 时,它会传播到所有依赖于它的 Deferred。可以在转换链中插入一个 recover 步骤,以便处理潜在的错误。

let transform = Deferred<(Int) -> Double, Never> { i throws in Double(7*i) }
let operand = Deferred<Int, Error>(value: 6).delay(seconds: 0.1)
let result = operand.apply(transform: transform)                        // Deferred<Double, Error>
print(result.value!)                                                    // 42.0

Deferredresult 属性(以及它的附属属性,valueerrorget())将阻塞当前线程,直到它的 Deferred 变得已解决。Deferred 的其余部分是无锁的。

Deferred 可以在指定的 DispatchQueue 上,或在请求的 DispatchQoS 上运行其闭包。notifymapflatMapapplyrecover 方法也具有这些选项。默认情况下,闭包将安排在以当前服务质量 (qos) 类创建的队列上。

任务执行调度

当存在请求时,任务执行。在创建 Deferred 对象时,会进行分配,但不会立即运行任何代码。当依赖于 Deferred 的代码请求结果时,任务会被安排执行。通过调用 notify()onValue()onError()beginExecution() 方法(非阻塞);以及 resultvalueerror 属性(阻塞)来发出请求。请求会沿着 Deferred 链向上传播。

长时间的计算、取消和超时

可以通过使用 Deferred 的回调样式初始化器轻松实现支持取消和超时的长时间后台计算。它接受一个闭包,该闭包的参数是一个 ResolverResolverDeferred 的特定实例相关联,并允许你的代码成为该 Deferred 实例的数据源。它还允许数据源监视 Deferred 的状态。

func bigComputation() -> Deferred<Double, Never>
{
  return Deferred {
    resolver in
    DispatchQueue.global(qos: .utility).async {
      var progress = 0
      repeat {
        // first check that a result is still needed
        guard resolver.needsResolution else { return }
        // then work towards a partial computation
        Thread.sleep(until: Date() + 0.001)
        print(".", terminator: "")
        progress += 1
      } while progress < 20
      // we have an answer!
      resolver.resolve(value: .pi)
    }
  }
}

// let the child `Deferred` keep a reference to our big computation
let timeout = 0.1
let validated = bigComputation().validate(predicate: { $0 > 3.14159 && $0 < 3.14160 })
                                .timeout(seconds: timeout, reason: String(timeout))

do {
  print(validated.state)       // still waiting: no request yet
  let pi = try validated.get() // make the request and wait for value
  print(" ", pi)
}
catch Cancellation.timedOut(let message) {
  print()
  assert(message == String(timeout))
}

在上面的例子中,我们的计算闭包努力计算一个圆的周长与其直径的比率,然后通过将其与一个已知的近似值进行比较来执行一个粗略的输出验证。请注意,执行主计算的 Deferred 对象不会被此用户代码直接保留。当超时被触发时,计算被正确地放弃,并且该对象被释放。通常的取消可以以类似的方式执行。

Deferred 被小心地编写以通过利用 Swift 运行时的引用计数来支持取消。在每种情况下,当从包中的函数返回一个新的 Deferred 时,该返回的引用是唯一存在的强引用。这允许取消为整个计算链正常工作,即使它被应用于链的最后一个链接,而不管错误类型如何。

在项目中使用 Deferred

使用 swift 包管理器,将以下内容添加到你的包清单的依赖项中

.package(url: "https://github.com/glessard/deferred.git", from: "6.7.0")

许可证

此库是在 MIT 许可证下发布的。有关详细信息,请参见 LICENSE。