0

I know that await does not directly spawn a new thread, but just tell the function to suspend so the system can utilize threads for other work. But when it comes to how / which thread the system will use to continue executing the awaited task / work is a bit unclear.

I'm testing this on Xcode 14, where I have 3 fetch versions. Upon running here are my observations

  • ViewModel.fetch1: run on main thread
  • ViewModel.fetch2: run on cooperative thread pool
  • ContentView.fetch: run on main thread
class ViewModel: ObservableObject {
    @Published var string = ""
    
    func fetch1() {
        let url = URL(string: "https://google.com")!
        let data = try! Data(contentsOf: url)
        self.string = String(data: data, encoding: .utf8) ?? ""
    }
    
    func fetch2() async {
        let url = URL(string: "https://google.com")!
        let data = try! Data(contentsOf: url)
        self.string = String(data: data, encoding: .utf8) ?? ""
    }
}

struct ContentView: View {
    @State var string = ""
    @StateObject var vm = ViewModel()

    var body: some View {
        VStack {
            Button {
                Task {
                    await vm.fetch1()
                }
                
                Task {
                    await vm.fetch2()
                }
                
                Task {
                    await fetch()
                }
            } label: {
                Text("Fetch")
            }
            
            Text(string)
            Text(vm.string)
        }
    }
    
    private func fetch() async {
        let url = URL(string: "https://google.com")!
        let data = try! Data(contentsOf: url)
        self.string = String(data: data, encoding: .utf8) ?? ""
    }
}

I know that using new async/await concurrency model, it's not wise to think about thread, but here I'm trying to make sure it does not run heavy work on the main thread.

For the ContentView.fetch and ViewModel.fetch1 methods being executed on the main thread, it's understandable that Task inherits the actor context, and since I make unstructured Task in SwiftUI body which is using main actor.

For the ViewModel.fetch2 method, I mark it as async, and somehow Swift decides to use cooperative thread pool instead of "inheriting" main actor context?

onmyway133
  • 45,645
  • 31
  • 257
  • 263
  • Does this answer your question? [Swfit: Trouble running async functions in background threads (concurrency)](https://stackoverflow.com/questions/73538764/swfit-trouble-running-async-functions-in-background-threads-concurrency) – lorem ipsum Oct 05 '22 at 11:51
  • @loremipsum I've read that, it does not answer my question – onmyway133 Oct 06 '22 at 06:52

1 Answers1

1

You asked:

Does await use main thread or cooperative thread pool?

The await doesn’t dictate the thread. It just introduces a suspension point in your code. It all comes down to what you are awaiting.

The issue is that Task { … } “[r]uns the given ... operation asynchronously as part of a new top-level task on behalf of the current actor.”

If you want it off the main actor, you should use Task.detached { … }.


That having been said, as the documentation for Data(contentsOf:) says:

Don't use this synchronous initializer to request network-based URLs. For network-based URLs, this method can block the current thread for tens of seconds on a slow network, resulting in a poor user experience, and in iOS, may cause your app to be terminated.

So, I would avoid Data(contentsOf:) for anything other than reading small files from your local file system. Instead, use URLSession:

func fetch3() async throws -> String {
    let url = URL(string: "https://google.com")!
    let (data, _) = try await URLSession.shared.data(from: url)
    return String(data: data, encoding: .utf8) ?? ""
}

That completely avoids the problem of needing to worry about which actor you run it from. It also offers cancelation capabilities and a number of other benefits.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Thanks for the answer Rob. I use `Data(contentsOf:)` here to understand how it will possibly block the main thread. What I find unclear is which main thread vs thread pool the system will use to execute the awaited task – onmyway133 Oct 06 '22 at 07:06
  • Don’t use the main actor if you have something slow and synchronous that you don’t want it blocking the main thread. So, that means detached tasks, or your own actors. – Rob Oct 06 '22 at 07:16