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

25

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

该课程使用Grand Central Dispatch(GCD)API演示多线程。但他们指出:

"自2021年WWDC以来,GCD已被Swift的新内置异步API大部分取代。"

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

在观看了苹果的WWDC视频之后,我认为DispatchQueue.global(qos: .userInitiated).async { }在这个新的异步API中被Task(priority: .userInitiated) {}Task { }所取代,但我不确定DispatchQueue.main.async { }被替换成了什么?

所以,我的问题如下:

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

请帮忙,我想学习这个新的异步等待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
    }
}

1
在您的原始代码中,可能不应该使用.userInteractive来处理Data(contentsOf: url)。相反,可以考虑使用异步URL方法,例如使用URLSession。请参考以下链接:https://www.hackingwithswift.com/books/ios-swiftui/sending-and-receiving-codable-data-with-urlsession-and-swiftui - jnpdx
4
如果你是一个初学者程序员,最好暂时不要使用async/await。此外,你在这里要求我们为你完成所有的工作,这并不是应有的方式。 - Joakim Danielson
1
你说得没错,使用URLSession是更好的方法来实现这个功能,但斯坦福教授暂时使用GCD来解释多线程。几节课后,他改用了URLSession - user14119170
6
我并不要求你做所有的工作。 我的问题是:
  1. 我是否正确地认为,DispatchQueue.global(qos:.userInitiated).async {}已被替换为Task(priority: .userInitiated) {}
  2. DispatchQueue.main.async {}已被替换成什么?
- user14119170
1
谢谢vadian! 我已经阅读了这篇文章,但没有找到答案。如果有人能够提供一些帮助,我将继续寻找我的第一个问题的答案,我会非常感激。 哦,我想知道我是否正确地认为我可以用Task(priority: .userInitiated) {}替换DispatchQueue.global(qos: .userInitiated).async { } - user14119170
显示剩余4条评论
1个回答

28

如果你真的要做一些缓慢而同步的事情,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)
    }
}

如果你想要在主线程上更新用户界面,而不是将其分派回主队列,那么你只需向更新用户界面的方法添加@MainActor修饰符:

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

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

总之,与其寻找旧GCD模式的一对一类比,尽可能使用苹果提供的并发API。


顺带一提,除了上面展示的@MainActor模式(作为调度到主队列的替代品)外,您还可以进行以下操作:

await MainActor.run {
    
}

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

在Swift的并发模型中,有一个名为main actor的全局actor协调主线程上的所有操作。我们可以用对MainActor的run函数的调用替换DispatchQueue.main.async。它接受一段要在MainActor上运行的代码块....

但他继续说道:

我可以使用@MainActor注释函数。这将要求调用者在运行此函数之前切换到主actor。...现在我们已经将该函数放置在主actor上,严格来说,我们不再需要MainActor.run了。


2
如果您想在后台线程中执行某些操作,然后在主线程中完成结果,而这与URLSession无关怎么办?user14119170的问题非常合理,因为DispatchQueue确实也用于此类情况。因此,在URLSession示例之外使用异步/等待并发进行后台线程的正确方法是什么? - Jonauz
1
正如我上面所概述的,分离任务是在后台线程上运行某些计算密集型任务的最简单方法。(您也可以使用演员来完成。)我提到URLSession只是想说明OP的同步网络请求示例是一个不好的用例。 - Rob

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接