为什么 @MainActor 中的任务不会阻塞 UI?

Why does a Task within a @MainActor not block the UI?

今天我将 SwiftUI 视图的 ViewModel 重构为结构化并发。它触发网络请求,当请求返回时,更新 @Published 属性 以更新 UI。因为我使用 Task 来执行网络请求,所以我必须回到 MainActor 来更新我的 属性,并且我正在探索不同的方法来做到这一点。一种直接的方法是在我的 Task 中使用 MainActor.run,效果很好。然后我尝试使用 @MainActor,但不太理解这里的行为。

有点简化,我的 ViewModel 看起来有点像这样:

class ContentViewModel: ObservableObject {
    
    @Published var showLoadingIndicator = false
    
    @MainActor func reload() {
        showLoadingIndicator = true
        
        Task {
            try await doNetworkRequest()
            showLoadingIndicator = false
        }
    }
    
    @MainActor func someOtherMethod() {
        // does UI work
    }
    
}

我原以为这不会正常工作。

首先,我预计 SwiftUI 会抱怨 showLoadingIndicator = false 发生在主线程之外。它没有。所以我设置了一个断点,似乎 @MainActor 中的 Task 在主线程上也是 运行。为什么这可能是另一天的问题,我想我还没有完全弄明白 Task。现在,让我们接受这个。

那么我会期望 UI 在我的 networkRequest 期间被阻塞 - 毕竟,它是 运行 在主线程上。但事实也并非如此。网络请求 运行s,UI 在此期间保持响应。即使调用主要参与者的另一个方法(例如 someOtherMethod)也完全正常。
即使 运行 在 doNetworkRequest 中使用 Task.sleep() 之类的东西仍然可以完全正常工作。这很好,但我想了解为什么。

我的问题:
a) 我假设 MainActor 中的 Task 不会阻止 UI 是否正确?为什么?
b) 这是一个明智的方法,还是我 运行 会因为使用 @MainActor 来分派异步工作而陷入困境?

await 是 Swift 中的屈服点。这是当前任务释放队列并允许其他内容 运行 的地方。所以在这一行:

        try await doNetworkRequest()

您的任务将放开主队列,并安排其他任务。它不会阻塞等待它完成的队列。

这意味着在 await returns 之后,其他代码可能已被主要参与者 运行,因此您不能相信属性或其他值您在 await.

之前缓存的先决条件

目前没有简单的 built-in 方式来表示“阻止此演员直到完成”。 Actor 是可重入的。