为什么@MainActor中的任务不会阻止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 在我的网络请求期间会被阻止 - 毕竟,它是在主线程上运行的。但事实也并非如此。网络请求运行,并且 UI 在此期间保持响应。即使调用主要参与者的另一个方法(例如 someOtherMethod
)也完全可以正常工作。
即使在 doNetworkRequest
中运行类似 Task.sleep()
的东西仍然可以完全正常工作。这很棒,但我想了解为什么。
我的问题:
a) 我假设 MainActor
中的 Task
不会阻塞 UI 是正确的吗?为什么?
b) 这是一种明智的方法吗?或者我使用 @MainActor
来分派这样的异步工作会遇到麻烦吗?
Today I refactored a ViewModel for a SwiftUI view to structured concurrency. It fires a network request and when the request comes back, updates a @Published
property to update the UI. Since I use a Task
to perform the network request, I have to get back to the MainActor
to update my property, and I was exploring different ways to do that. One straightforward way was to use MainActor.run
inside my Task
, which works just fine. I then tried to use @MainActor
, and don't quite understand the behaviour here.
A bit simplified, my ViewModel would look somewhat like this:
class ContentViewModel: ObservableObject {
@Published var showLoadingIndicator = false
@MainActor func reload() {
showLoadingIndicator = true
Task {
try await doNetworkRequest()
showLoadingIndicator = false
}
}
@MainActor func someOtherMethod() {
// does UI work
}
}
I would have expected this to not work properly.
First, I expected SwiftUI to complain that showLoadingIndicator = false
happens off the main thread. It didn't. So I put in a breakpoint, and it seems even the Task
within a @MainActor
is run on the main thread. Why that is is maybe a question for another day, I think I haven't quite figured out Task
yet. For now, let's accept this.
So then I would have expected the UI to be blocked during my networkRequest - after all, it is run on the main thread. But this is not the case either. The network request runs, and the UI stays responsive during that. Even a call to another method on the main actor (e.g. someOtherMethod
) works completely fine.
Even running something like Task.sleep()
within doNetworkRequest
will STILL work completely fine. This is great, but I would like to understand why.
My questions:
a) Am I right in assuming a Task
within a MainActor
does not block the UI? Why?
b) Is this a sensible approach, or can I run into trouble by using @MainActor
for dispatching asynchronous work like this?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
await
是 Swift 中的屈服点。这是当前任务释放队列并允许其他任务运行的地方。因此,在这一行:您的任务将释放主队列,并让其他事情被安排。它不会阻塞等待其完成的队列。
这意味着在
await
返回后,主要参与者可能已经运行了其他代码,因此您不能信任在
之前缓存的属性值或其他前提条件等待。
目前没有简单的内置方式来表示“阻止此演员直到完成”。演员是可重入的。
await
is a yield point in Swift. It's where the current Task releases the queue and allows something else to run. So at this line:your Task will let go of the main queue, and let something else be scheduled. It won't block the queue waiting for it to finish.
This means that after the
await
returns, it's possible that other code has been run by the main actor, so you can't trust the values of properties or other preconditions you've cached before theawait
.Currently there's no simple, built-in way to say "block this actor until this finishes." Actors are reentrant.