简体   繁体   中英

Swift task and await/async queue after `await` execution?

I am trying to understand what the rule is when calling Task {... } and, in that task, calling await in terms of threads.

This example works:

struct TaskTestView: View {
    
    let url = URL(string: "https://www.google.com")!
    @State private var message = "Loading..."

    var body: some View {
        Text(message)
            .task {
                /// on MAIN THREAD
                do {
                    var receivedLines = [String]()
                    for try await line in url.lines {
                 /// on MAIN THREAD
                        receivedLines.append(line)
                        message = "Received \(receivedLines.count) lines"
                    }
                } catch {
                    message = "Failed to load"
                }
            }
    }
}

This does not:

struct TaskTestView: View {

    @StateObject var model = TaskTestViewModel()
    
    var body: some View {
        Text(model.message)
            .task {
                /// - here it is on main thread
                await model.refresh()
                /// - here it is NOT on main thread
                print("after refresh: on main?")
            }
    }
}

class TaskTestViewModel:ObservableObject {
    
    let url = URL(string: "https://www.google.com")!
    @Published private(set) var message = "Loading..."
    
    func refresh() async {
        
        do {
            var receivedLines = [String]() // on main thread
            for try await line in url.lines {
                receivedLines.append(line) // NOT on main thread
                message = "Received \(receivedLines.count) lines"
            }
        } catch {
            message = "Failed to load"
        }
        
    }
    
}
  1. Why does the code run on the main thread after line for try await line in url.lines { in the first example?
  2. Why does the code NOT run on the main thread after that same line but in the second example?

How would I know the answer to those questions without running the code and putting a breakpoint to inspect the thread I am on?

Obviously the main issue here is that I want to make sure I update @State variables on main so the view works properly, but, not having this concept clear makes it hard to design proper patterns.

There are not clear rules regarding on which thread the “continuation” will run. WWDC 2021 video Swift concurrency: Behind the scenes talks about continuations and how the thread after the suspension point might be different than the one before.

Trying to diagnose this with traditional debugging statements or breakpoints can be a frustrating exercise. I have encountered situations where the insertion of a few completely unrelated lines of debugging code changed the continuation threading behavior. Bottom line, unless you tell it otherwise, it is free to decide on which thread the continuation will run and it can sometimes defy expectations.

But, if you want to make sure that your observable object updates on the main thread, you can use the @MainActor qualifier:

@MainActor
class TaskTestViewModel: ObservableObject {
    ... 
}

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