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

How can I convert to Swift async/await from GCD (DispatchQueue)?

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

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

"GCD has been mostly replaced by Swift's new built-in async API as of WWDC 2021".

所以我想了解讲座中的代码在更新它以使用这个新的 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. 是否正确
  3. 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)
            }
        }
    }
}

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

private func fetchBackgroundImageDataIfNecessary() {
    backgroundImage = nil
    switch emojiArt.background {
    case .url(let url):
        // fetch the url
        backgroundImageFetchStatus = .fetching
        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)
                    }
                }
            }
        }
    case .imageData(let data):
        backgroundImage = UIImage(data: data)
    case .blank:
        break
    }
}

如果你真的要做一些慢速和同步的事情,Task.detached 更接近于 GCD 调度到全局队列。如果您只使用 Task(priority: ...) { ... } ,则由并发系统自行决定将其 运行 放在哪个线程上。 (仅仅因为您指定了较低的 priority 并不能保证它可能不会 运行 在主线程上。)

例如:

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

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

@MainActor
func updateUI(with image: UIImage?) async {
    imageView.image = image
}

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

简而言之,不要为旧的 GCD 模式寻找一对一的类比,而是尽可能使用 Apple 提供的并发 API。


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

await MainActor.run {
    ...
}

这大致类似于调度到主队列。他们在 WWDC 2021 视频中说 Swift concurrency: Update a sample app

In Swift’s concurrency model, there is a global actor called the main actor that coordinates all operations on the main thread. We can replace our dispatch main.async with a call to MainActor's run function. This takes a block of code to run on the MainActor. run is an async function, so we need to await it. Awaiting is necessary because this function may need to suspend until the main thread is ready to process this operation. But because