为什么 @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 是可重入的。
今天我将 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 是可重入的。