Who decides which actor code runs on? 谁决定在哪个 actor 代码上运行?

Who decides which actor code runs on? 谁决定在哪个 actor 代码上运行?

温馨提示:本文最后更新于2025-02-08 18:02:19,某些文章具有时效性,若有错误或已失效,请在下方留言

One common confusion with actors comes in deciding exactly where code runs, because just adding @MainActor and hoping for the best is rarely good enough.
与 Actor 的一个常见混淆是决定代码的确切运行位置,因为仅仅添加@MainActor并希望得到最好的结果很少是不够的。

A common problem is code like this:
一个常见的问题是这样的代码:

Task { @MainActor in 
    await downloadData()
}

That does three things at once:
这同时做三件事:

  1. It creates a new task to run some asynchronous code.
    它会创建一个新任务来运行一些异步代码。
  2. It instructs the body of that task to run on the main actor.
    它指示该任务的主体在主角色上运行。
  3. It calls a separate asynchronous function called downloadData().
    它调用一个名为 downloadData() 的单独异步函数。

The confusion here is simple: you might think downloadData() will run on the main actor, but from the code above we have no idea whether that will be true or not – it could run on any actor.
这里的混淆很简单:您可能认为 downloadData() 将在 main actor 上运行,但从上面的代码中我们不知道这是否属实 – 它可以在任何 actor 上运行。

To understand why, remember the rule that every time you see the await keyword it marks a potential suspension point – when Swift reaches an await, it’s free to transfer execution wherever it needs to.
要理解其中的原因,请记住这样一条规则:每次您看到 await 关键字时,它都会标记一个潜在的暂停点 – 当 Swift 到达 await 时,它可以自由地将执行转移到任何需要的地方。

So, perhaps downloadData() is defined like this:
所以,也许 downloadData() 是这样定义的:

func downloadData() async {
    // do work here
}

That hasn’t specified where the work should be done, so Swift is free to choose – and it’s very likely to do it away from the main actor.
这并没有指定应该在哪里完成工作,因此 Swift 可以自由选择——而且它很可能会远离主要参与者进行选择。

Alternatively, perhaps it’s defined like this:
或者,也许它的定义是这样的:

@MainActor
func downloadData() async {
    // do work here
}  

…in which case it will run on the main actor regardless. The same goes for if it belongs to an actor-isolated type, like this:
…在这种情况下,它将无论如何在 Main Actor 上运行。如果它属于 actor 隔离类型,情况也是如此,如下所示:

@MainActor
class DataFetcher {
    func downloadData() async {
        // do work here
    }
}

This isn’t a bug, it’s completely intentional: code that you want to run on the main actor or indeed any actor must be marked as such – the code being called gets to decide where it runs, rather than the caller.
这不是一个 bug,它完全是有意为之:你想在 main actor 或任何 actor 上运行的代码必须被标记为 —— 被调用的代码可以决定它的运行位置,而不是调用者。

Let’s return to the problem code again:
让我们再次回到问题代码:

Task { @MainActor in 
    await downloadData()
}

If downloadData() isn’t guaranteed to run on the main actor, what exactly is the @MainActor annotation doing? The answer is: nothing at all. Using @MainActor in with a task means “the body of this closure must run on the main actor, but any async code we call can run elsewhere.”
如果不能保证 downloadData() 在主 actor 上运行,那么 @MainActor 注解究竟是做什么的呢?答案是:什么都没有。在 task 中使用 @MainActor 意味着“这个闭包的主体必须在 main actor 上运行,但我们调用的任何异步代码都可以在其他地方运行。

So, synchronous code in the closure will definitely run on the main actor, as you can see here:
所以,闭包中的同步代码肯定会在主 actor 上运行,正如你在这里看到的:

Task {
    print("This will be on the main actor")
    await downloadData()
    print("This will also ben on the main actor")
}

The mistake is thinking that downloadData() will also run on the main actor, because unless we see how it’s defined we simply can’t tell.
错误在于认为 downloadData() 也会在 main actor 上运行,因为除非我们看到它是如何定义的,否则我们根本无法判断。

The rule to remember is this: async functions get to choose where they run, irrespective of how you call them.
要记住的规则是这样的:async 函数可以选择它们的运行位置,而不管你如何调用它们。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享