swift 将DispatchQueue.global().async转换为async/await以实现同步功能

wb1gzix0  于 2023-05-16  发布在  Swift
关注(0)|答案(2)|浏览(160)

我正在将我的旧项目从DispatchQueue转换为async await,我在全局调度队列中 Package 了一个耗时的操作:

DispatchQueue.global().async {
  self.processed = processData(input)
  DispatchQueue.main.async {
    render() // reload UI with self.processed
  }
}
  • processData()是耗时的同步操作
  • render()使用处理后的数据更新UI,并且需要在主线程上

看起来最接近全局队列的是使用Task.detached,但是使用它我不能改变主参与者隔离的属性,例如。self.processed
然后我想到这样做:

processData(input: Input) async -> Output {
  await withCheckedContinuation { continuation in
    DispatchQueue.global().async {
      let output = process(input)
      continuation.resume(returning: output)
    }
  }
}
…
let processed = await processData(input)
render()

但感觉就像是为了使用async/await而使用DispatchQueue。有什么想法吗谢谢!

thtygnil

thtygnil1#

是的,理论上可以使用分离任务。
只需确保包含此代码的方法与主actor隔离,以及render方法,但processData方法不是。例如,在一个本身是actor isolated的类型中,你只需要将processData标记为nonisolated

@MainActor
final class Foo: Sendable {
    var processed: Output?

    func processAndUpdateUI(for input: Input) async throws {
        processed = try await Task.detached {
            try self.processData(input)
        }.value

        render()
    }

    func render() {…}

    nonisolated func processData(_ input: Input) throws -> Output {…}
}

但我会明确建议不要使用MainActor.run {…}Task { @MainActor in …}模式。太脆了。找到合适的演员的负担不应该落在打电话的人的肩上。只要确保相关的方法和属性是参与者隔离的,编译器将确保您正确调用这些方法,跨越参与者隔离边界的值是Sendable等。
关于后一点的说明。当你在线程之间传递对象时,你必须让编译器知道它们是线程安全的,即Sendable。WWDC 2022视频Eliminate data races using Swift Concurrency在Swift 5.x中,如果你的类型是Sendable,它不会总是警告你,所以考虑将“严格并发检查”构建设置更改为“完成”:

第一次或第二次处理Sendable类型时,它可能看起来非常混乱,但希望上面的视频会有所帮助。但是一旦您掌握了Sendable一致性,它将成为您的第二天性,并且您会想知道为什么您要经历过去所有那些线程安全问题。
问题是分离的任务是非结构化的并发。也就是说,如果您取消父任务,它不会将取消传播到子任务。
所以,我可能会留在结构化并发中。例如,我可以将processData移动到它自己的actor中:

@MainActor
final class Bar: Sendable {
    private let processor = Processor()
    var processed: Output?

    func processAndUpdateUI(for input: Input) async throws {
        processed = try await processor.processData(input)
        render()
    }

    func render() {…}
}

actor Processor {
    func processData(_ input: Input) throws -> Output {
        while … {
            try Task.checkCancellation() // periodically check to make sure this hasn’t been canceled

            …
        }

        return output
    }
}
hjqgdpho

hjqgdpho2#

您可以使用任务与后台优先级类似:

Task.detached(priority: .background) {
  let output = await processData(input:yourInput)
  await MainActor.run {
    render()
  }
}

func processData(input: Input) async -> Output {
    //do your expensive processing
}

相关问题