【问题标题】:Swift Concurrency - How to get a Result from a task not workingSwift Concurrency - 如何从不工作的任务中获取结果
【发布时间】:2021-10-31 15:18:43
【问题描述】:

我试图把这个例子放到一个简单的项目 + 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 错误的屏幕截图:

【问题讨论】:

  • 请将您的代码和错误消息发布为文本而不是图像。
  • 将结果集设置为类变量
  • 我已将代码添加为文本(我希望编译器错误显示抱歉!)

标签: swift concurrency task


【解决方案1】:

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

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

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

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

但是,如果您将对 fetchQuotes 的调用包装在 Task 初始化程序中,则 async 上下文。成功!


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

【讨论】:

  • 您好,感谢您的回答。我说我已经知道我可以将 fetchQuotes 包装在一个任务中。我在问如何调用已经创建任务而不创建另一个任务的 fetchQuotes。这是一种代码气味。谢谢
【解决方案2】:

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

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

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

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

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

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

,可以从你的@IBAction调用

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

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

【讨论】:

    猜你喜欢
    • 2021-12-15
    • 1970-01-01
    • 1970-01-01
    • 2017-02-20
    • 1970-01-01
    • 2013-12-27
    • 2018-04-15
    • 2021-05-29
    • 2016-11-11
    相关资源
    最近更新 更多