Swift 并发 - 如何从不工作的任务中获取结果

Swift Concurrency - How to get a Result from a task not working

我试图将此示例放入一个简单的项目中 + ViewController 但无法编译 https://www.hackingwithswift.com/quick-start/concurrency/how-to-get-a-result-from-a-task

我正在尝试通过点击按钮从 IBAction 调用 fetchQuotes(),但是因为 fetchQuotes 被标记为 async,我收到了一个错误。

我知道我可以将对 fetchQuotes() 的调用包装在一个任务中:

Task { 
  fetchQuotes() 
}

,但这对我来说没有意义,因为 fetchQuotes 已经在创建任务。

有人可以指点一下吗?

这是我的代码:

// https://www.hackingwithswift.com/quick-start/concurrency/how-to-get-a-result-from-a-task

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

    }

    @IBAction func buttonTap(_ sender: Any) {
       fetchQuotes()
    }

    func fetchQuotes() async {
        let downloadTask = Task { () -> String in
            let url = URL(string: "https://hws.dev/quotes.txt")!
            let data: Data

            do {
                (data, _) = try await URLSession.shared.data(from: url)
            } catch {
                throw LoadError.fetchFailed
            }

            if let string = String(data: data, encoding: .utf8) {
                return string
            } else {
                throw LoadError.decodeFailed
            }
        }

        let result = await downloadTask.result

        do {
            let string = try result.get()
            print(string)
        } catch LoadError.fetchFailed {
            print("Unable to fetch the quotes.")
        } catch LoadError.decodeFailed {
            print("Unable to convert quotes to text.")
        } catch {
            print("Unknown error.")
        }
    }
}

enum LoadError: Error {
    case fetchFailed, decodeFailed
}

这是我的代码截图 + Xcode 错误:

but this doesn't make sense to me since fetchQuotes is already creating tasks

无关;你已经内化了一个错误的规则,或者没有内化真正的规则。这与谁在“创建任务”无关。它与真正的规则有关,Swift的async/await最基本的规则是:

您只能从 async 上下文中调用 async 方法。

嗯,方法 buttonTap 不是 async 方法,因此您对 fetchQuotes 的调用不是在 async 上下文中进行的。这是非法的,编译器会阻止你。

但是,如果您将对 fetchQuotes 的调用包装在任务初始值设定项中,则 一个 async 上下文。成功!


基本上,您看到的效果就是我所说的厄运倒退。由于对 async 方法的每次调用都必须在 async 上下文中,但由于 您的 来自 Cocoa 的事件,例如 IBAction,是 不是 async你的代码怎么可能调用async方法?答案是任务初始化程序:它为您提供了一个开始的地方。

this doesn't make sense to me since fetchQuotes is already creating tasks

fetchQuotes内部做的是一个实现细节,它还不如不显式创建任何任务,甚至不进行异步调用。

现在,方法声明的 async 部分告诉编译器该方法可能会挂起调用者线程,因此任何调用者在调用该方法时都需要提供异步上下文。请注意,我说的是 可能暂停 而不是 将暂停 ,这是一个微妙但重要的区别。

该方法创建的任务仅在方法执行期间有效,因此如果您需要能够从非异步上下文中调用该方法,则必须创建新任务。

但是,您可以通过提供非异步重载来整理代码:

func fetchQuotes() {
    Task {
        await fetchQuotes()
    }
}

,可以从您的 @IBAction

调用
@IBAction func buttonTap(_ sender: Any) {
    fetchQuotes()
}

无法逃避这一点,如果您希望能够从非异步上下文中调用异步函数,那么您将需要一个 Task,无论调用的方法是如何实现的.结构化并发任务不可重用。