简体   繁体   中英

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

I tried to put this example into a simple project + ViewController but can't get it to compile https://www.hackingwithswift.com/quick-start/concurrency/how-to-get-a-result-from-a-task

I'm trying to call fetchQuotes() from an IBAction via a button tap, however because fetchQuotes is marked async , I'm getting an error.

I know that I can wrap the call to fetchQuotes() in a task:

Task { 
  fetchQuotes() 
}

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

Can anyone advise?

Here is my code:

// 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
}

Here's a screenshot of my code + Xcode error: 在此处输入图片说明

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

Irrelevant; you've internalized a false rule, or failed to internalize the real rule. This has nothing to do with who is "creating tasks". It has to do with the real rule, the most fundamental rule of Swift's async/await , which is:

You can only call an async method from within an async context.

Well, the method buttonTap is not an async method, so your call to fetchQuotes is not being made within an async context. That's illegal, and the compiler stops you dead.

But if you wrap the call to fetchQuotes in a Task initializer, that is an async context. Success!


Basically, the effect you're seeing is what I call the regress of doom . Since every call to an async method must be in an async context, but since your events from Cocoa, such as an IBAction, are not async , how can your code ever call an async method? The answer is a Task initializer: it gives you a place to get started.

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

What fetchQuotes is internally doing is an implementation detail, it might as well not explicitly create any tasks, or not even be doing async calls.

Now, the async part of the method declaration tells the compiler the method might suspend the caller thread, thus any caller needs to provide an async context when calling the method. Note that I said might suspend not will suspend , this is a subtle, but important difference.

The tasks created by the method are valid only during the method execution, so if you need to be able to call the method from a non-async context you'll have to create new tasks.

However, you can tidy-up your code by providing a non-async overload:

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

, which can be called from your @IBAction

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

There's no escaping out of this, if you want to be able to call an async function from a non-async context, then you'll need a Task , regardless on how the called method is implemented. Structured concurrency tasks are not reusable.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM