在调用声明为@MainActor 的异步函数的任务中使用@MainActor

Using @MainActor in a Task which is calling async function declared as @MainActor

考虑到以下代码块,我想了解是否需要将任务本身声明为 MainActor。

func login() {
    Task { [weak self] in
        let result = await self?.loginService.start()

        if result == .successful {
            self?.showWelcomeMessage() // updating the UI here
        }
    }
}

final class LoginService {

    @MainActor
    func start() async -> LoginResult {
        // Doing some UI related operations here
    }
}

当在 Task 内部调用的异步函数已经声明为 MainActor 时,是否还需要将 Task 本身声明为 MainActor?像这样:

func login() {
    Task { @MainActor [weak self] in
        let result = await self?.loginService.start()

        if result == .successful {
            self?.showWelcomeMessage() // updating the UI here
        }
    }
}

我相信如果一个 Task 本身被声明为 MainActor,如果没有另外声明,子异步操作将继承父配置,但它是否也可以反过来工作?

你说:

I believe if a Task itself is declared as MainActor, child async operations will inherit the parent’s configuration if not declared otherwise, …

是的,但我会谨慎地假设这一点,因为 child 的 actor 上下文经常像您所说的那样“以其他方式声明”。编写依赖于这些假设的代码是轻率的:我们希望我们的类型尽可能松散耦合。

… but does it also work the other way around?

不,parent一般不会继承child的actor context。

例外情况是,如果 parent 没有声明特定的 actor 上下文,它的延续(await 之后的代码)可能 运行 在 child 的演员正在使用(不过,IIRC,对此没有正式的保证,我建议不要依赖任何此类假设)。

但是 Swift 并发的美妙之处在于我们不应该关心 child 使用什么(例如,登录服务使用什么 actor 上下文),因为任何代码(例如“欢迎消息”的呈现)要求特定参与者应该简单地声明自己。 “显示欢迎消息”功能的职责是指示它需要主要参与者,而不是调用者。

如果每个 child 在各自适当的 actor 上下文中成功 运行,那么 login 方法现在根本不需要担心这个。

Do I need to declare the Task itself as MainActor too when the async function which is called inside the Task is declared as MainActor already? Like this:

func login() {
   Task { @MainActor [weak self] in
       let result = await self?.loginService.start()

       if result == .successful {
           self?.showWelcomeMessage() // updating the UI here
       }
   }
}

你可以做到。

就个人而言,如上所述,我会简单地用 @MainActor 注释 showWelcomeMessage(或整个 class,如果有意义的话),而不是这个闭包。必须在主要角色上显示消息,而不是结果检查代码。 login 现在不需要对登录服务或“显示消息”例程进行任何假设。