如何从 GCD (DispatchQueue) 转换为 Swift async/await?

use*_*170 26 grand-central-dispatch async-await swift

我正在关注斯坦福大学的 CS193p 开发 iOS 应用程序在线课程。

它使用 Grand Central Dispatch (GCD) API 来演示多线程。但他们指出,

“自 WWDC 2021 起,GCD 已大部分被 Swift 新的内置异步 API 取代”。

因此,我想了解讲座中的代码在更新以使用这个新 API 后会是什么样子。

观看 Apple 的 WWDC 视频后,在我看来,
DispatchQueue.global(qos: .userInitiated).async { }这个新的异步 API 中被替换为Task { }Task(priority: .userInitiated) {},但我不确定,被DispatchQueue.main.async { }替换为什么?

所以,我的问题是:

  1. 我是否正确地假设,它DispatchQueue.global(qos: .userInitiated).async { }已被替换为Task(priority: .userInitiated) {}
  2. 被什么DispatchQueue.main.async { }取代了?

请帮忙,我想学习这个新的 async-await API。

以下是讲座中的代码,使用旧的 GCD API:

DispatchQueue.global(qos: .userInitiated).async {
    let imageData = try? Data(contentsOf: url)
    DispatchQueue.main.async { [weak self] in
        if self?.emojiArt.background == EmojiArtModel.Background.url(url) {
            self?.backgroundImageFetchStatus = .idle
            if imageData != nil {
                self?.backgroundImage = UIImage(data: imageData!)
            }
            // L12 note failure if we couldn't load background image
            if self?.backgroundImage == nil {
                self?.backgroundImageFetchStatus = .failed(url)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

整个函数(如果您需要查看更多代码):

DispatchQueue.global(qos: .userInitiated).async {
    let imageData = try? Data(contentsOf: url)
    DispatchQueue.main.async { [weak self] in
        if self?.emojiArt.background == EmojiArtModel.Background.url(url) {
            self?.backgroundImageFetchStatus = .idle
            if imageData != nil {
                self?.backgroundImage = UIImage(data: imageData!)
            }
            // L12 note failure if we couldn't load background image
            if self?.backgroundImage == nil {
                self?.backgroundImageFetchStatus = .failed(url)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Rob*_*Rob 31

如果您确实要做一些缓慢且同步的事情,Task.detached则更接近于 GCD\xe2\x80\x99s 调度到全局队列。如果您只是使用Task(priority: ...) { ... }它,则将其留给并发系统自行决定在哪个线程上运行它。(仅仅因为您指定了较低的值priority并不能保证它不会在主线程上运行。)

\n

例如:

\n
func fetchAndUpdateUI(from url: URL) {\n    Task.detached {                          // or specify a priority with `Task.detached(priority: .background)`\n        let data = try Data(contentsOf: url)\n        let image = UIImage(data: data)\n        await self.updateUI(with: image)\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

如果您想在主线程上进行 UI 更新,而不是将其分派回主队列,您只需将修饰符添加@MainActor到更新 UI 的方法中即可:

\n
@MainActor\nfunc updateUI(with image: UIImage?) async {\n    imageView.image = image\n}\n
Run Code Online (Sandbox Code Playgroud)\n

话虽如此,这是一种非常不寻常的模式(同步执行网络请求并创建一个分离的任务以确保不会阻塞主线程)。我们可能会使用URLSession\xe2\x80\x99s 新的异步data(from:delegate:)方法来异步执行请求。它提供更好的错误处理、更好的可配置性、参与结构化并发并且是可取消的。

\n

简而言之,与其寻找旧 GCD 模式的一对一模拟,不如尽可能使用 Apple 提供的并发 API。

\n
\n

FWIW,除了@MainActor上面显示的模式(作为调度到主队列的替代)之外,您还可以执行以下操作:

\n
await MainActor.run {\n    \xe2\x80\xa6\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这大致类似于调度到主队列。在 WWDC 2021 视频Swift 并发:更新示例应用程序中,他们说:

\n
\n

在 Swift\xe2\x80\x99s 并发模型中,有一个称为主参与者的全局参与者,它协调主线程上的所有操作。我们可以将 our 替换为对\xe2\x80\x99s函数DispatchQueue.main.async的调用。这需要一段代码才能在. \xe2\x80\xa6MainActorrunMainActor

\n
\n

但他接着说:

\n
\n

我可以用 注释函数@MainActor。这将要求调用者在运行该函数之前切换到主要参与者。\xe2\x80\xa6 既然我们已经把这个功能放在了主角身上,严格来说,我们就MainActor.run不再需要这个了。

\n
\n

  • 如果您想在后台线程中执行某些操作,然后在 main 中完成结果,而这与“URLSession”无关,该怎么办?**user14119170** 问题确实很合理,因为`DispatchQueue`确实也用于这种情况。因此,在“URLSession”示例之外使用异步/等待并发执行后台线程的正确方法是什么? (2认同)