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

发布于 2025-01-17 14:39:08 字数 1335 浏览 4 评论 0原文

今天,我将 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

蓝梦月影 2025-01-24 14:39:08

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

        try await doNetworkRequest()

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

这意味着在 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:

        try await doNetworkRequest()

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 the await.

Currently there's no simple, built-in way to say "block this actor until this finishes." Actors are reentrant.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文