将 DispatchQueue.global().async 转换为同步函数的 async/await

Converting DispatchQueue.global().async to async/await for synchronous function

提问人:Heuristic 提问时间:5/10/2023 更新时间:5/11/2023 访问量:202

问:

我正在将我的旧项目从 DispatchQueue 转换为异步等待,我将其包装在全局调度队列中,这是一个耗时的操作:

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

看起来最接近全局队列的东西是使用 ,但是使用它我无法改变主要参与者隔离的属性,例如.Task.detachedself.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

iOS Swift 异步 async-await 并发

评论


答:

0赞 Mr.SwiftOak 5/10/2023 #1

同样,您可以使用具有后台优先级的任务:

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

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

评论

0赞 Heuristic 5/10/2023
问题是我需要将处理后的数据存储到实例属性中,这是不允许的,因为它是主要参与者隔离的。即。.self.output = await processeData(input: yourInput)
0赞 Rob 5/11/2023 #2

是的,理论上您可以使用分离的任务。

只需确保具有此代码的方法与主要参与者以及方法隔离,但方法不是。例如,在一个类型中,它本身是 actor 隔离的,那么你只需要标记为:renderprocessDataprocessDatanonisolated

@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 {…}
}

但我明确建议不要使用 or 模式。这很脆弱。找到合适的演员的重担不应该落在呼叫者的肩上。只需确保相关方法和属性是 actor 隔离的,编译器将确保您正确调用这些方法,跨越 actor 隔离边界的值为 ,等等。MainActor.run {…}Task { @MainActor in …}Sendable

关于后一点的说明。当你在线程之间传递对象时,你必须让编译器知道它们是线程安全的,即 .观看 WWDC 2022 视频:使用 Swift Concurrency 消除数据争用。在 Swift 5.x 中,它不会总是警告你是否是你的类型,所以请考虑将 “Strict Concurrency Checking” 构建设置更改为 “Complete”:SendableSendable

enter image description here

第一次或两次处理类型时,这似乎非常令人困惑,但希望上面的视频会有所帮助。但是,一旦你掌握了一致性,它就会成为第二天性,你会想知道为什么你经历了过去所有那些令人头疼的线程安全问题。SendableSendable


问题在于分离的任务是非结构化并发的。也就是说,如果取消父任务,它不会将取消传播到子任务。

因此,我可能会保留在结构化并发中。例如,我可能会进入它自己的 actor:processData

@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
    }
}