Swift 并发模型中的“当前参与者”是什么?

Swe*_*per 10 concurrency actor swift

阅读Swift 指南的并发部分,我发现:

\n
\n

要创建在当前参与者上运行的非结构化任务,请调用Task.init(priority:operation:)初始化程序。要创建不属于当前参与者的非结构化任务(更具体地称为分离任务),请调用类Task.detached(priority:operation:)方法。

\n
\n

有“现任演员”这个词。文档中也提到了这一点Task.init

\n
\n

代表当前参与者异步运行给定的非抛出操作,作为新的顶级任务的一部分。

\n
\n

之前,我以为我已经明白“现任演员”是什么意思了:

\n
    \n
  • 如果代码在主队列上运行,那么当前的参与者可以被认为是MainActor,因为它的执行者是主队列。
  • \n
  • 如果代码标有全局参与者属性,则其当前参与者就是该全局参与者
  • \n
  • 如果代码位于 an 中actor且不是nonisolated,则其当前参与者就是该参与者。
  • \n
\n
@SomeOtherActor\nfunc somethingElse() {\n    DispatchQueue.main.async {\n        // current actor is MainActor\n    }\n}\n\n@MainActor\nclass Foo: NSObject {\n    func foo() { /* current actor is MainActor */ }\n}\n\nactor Bar {\n    func foo() { /* current actor is Bar */ }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

然而,最近我意识到我忘记考虑一种情况 - 全局队列(或任何其他随机队列)上会发生什么?

\n
@SomeOtherActor\nfunc somethingElse() {\n    DispatchQueue.global().async {\n        // current actor is?\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

当我尝试访问闭包中的参与者隔离属性之一时SomeOtherActor,我收到消息:

\n
\n

与全局参与者“SomeOtherActor”隔离的属性“x”无法从非隔离上下文中发生突变

\n
\n

这是斯威夫特表达没有现任演员的方式吗?如果是的话,会Task.init做什么呢?文档并没有真正说明。

\n

理想情况下,有没有办法以编程方式打印当前演员?

\n

我认为 SE 提案会解释“当前参与者”的含义没有一个 SE 提案提到“当前参与者”一词:SE-0306SE-0313SE-0316SE-0327SE-0344

\n

Rob*_*Rob 4

我怀疑您现在已经解决了您的问题,但为了未来的读者,让我提供一个答案。

\n

你建议:

\n
\n

如果代码在主队列上运行,那么当前的参与者可以被认为是MainActor,因为它的执行者是主队列。

\n
\n

不一定如此。仅仅因为某些东西在主队列(或主线程)上运行并不意味着它与主要参与者隔离。现在,UIViewController、 SwiftUIbody等都与主要参与者隔离,因此,我们从 UI 启动的许多内容都会自动与参与者隔离。但是您可以轻松地将非隔离函数(显式标记为的函数nonisolated或非隔离类型或非隔离闭包中的随机方法)引入到流程中。

\n

让\xe2\x80\x99s考虑一些你的例子。第一的:

\n
@SomeOtherActor\nfunc somethingElse() {\n    DispatchQueue.main.async {\n        // is this isolated to the main actor?\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

似乎存在一些编译器优化(至少在 Swift 5.9 中),编译器可以通过这些优化成功地推断出这是主要参与者(尽管缺少任何@MainActor限定符)。但请考虑以下事项:

\n
@SomeOtherActor\nfunc somethingElse() {\n    let queue: DispatchQueue = .main\n    queue.async {\n        self.baz()     // some stuff isolated to the main actor\n        self.qux = 1\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这看起来非常相似,但编译器不再能够推断出您与主要参与者隔离(至少在这一点上)。无论如何,您现在可能会收到警告:

\n
\n

在同步非隔离上下文中调用主参与者隔离实例方法“baz()”;这是 Swift 6 中的一个错误

\n
\n

(您可能需要将 \xe2\x80\x9cStrict Concurrency Checking\xe2\x80\x9d 构建设置设置为 \xe2\x80\x9cComplete\xe2\x80\x9d。)

\n

在此输入图像描述

\n

最重要的是,仅仅因为您位于主队列/线程上并不能确保您与主要参与者隔离。发生这种情况时,如果您调用的方法/属性与主要参与者隔离,编译器会推断滥用(至少使用 \xe2\x80\x9cStrict Concurrency Checking\xe2\x80\x9d 构建设置),并警告您。

\n

现在,我承认我上面的例子queue.async {\xe2\x80\xa6}有点做作。但是有很多例子,您可能会意外地引入非隔离上下文(例如,一些不是参与者隔离的帮助器类,一些遗留@preconcurrency上下文/回调等),其中 \xe2\x80\x9c 的一般警告只是因为我可能在主线程上,它并不\xe2\x80\x99t意味着我\xe2\x80\x99m隔离到主要演员\xe2\x80\x9d适用。但是,如果您确保类型、方法和属性正确隔离,那么编译器将从那里处理它,而无需在调用点进行任何额外的修饰。

\n
\n

现在,考虑到我们应该(除了少数例外)在过渡到 Swift 并发期间停用 GCD API,如果您希望它在主要参与者上,理论上您可以将 aTask与主要参与者隔离:

\n
@SomeOtherActor\nfunc somethingElse() {\n    Task { @MainActor in\n        // current actor is MainActor\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

但这是一种非常 GCD 的逻辑思维方式(将负担放在调用者身上以确保东西在主要参与者上运行)。它还不必要地引入了非结构化并发

\n

通常最好将需要在主要参与者上运行的内容标记为这样,然后调用它(通过编译时验证代码正确性):

\n
@SomeOtherActor\nfunc somethingElse() async {\n    \xe2\x80\xa6\n    await somethingThatRequiresMainActor()\n    \xe2\x80\xa6\n}\n\n@MainActor\nfunc somethingThatRequiresMainActor() {\n    \xe2\x80\xa6\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我们的想法是,我们应该简单地隔离需要在特定参与者上运行的方法(或者,更干净一点,将它们放入与适当参与者隔离的类型中;这样你就不必装饰所有单独的参与者隔离的方法)并让编译器检查代码的正确性。这样,我们就可以享受代码的编译时验证,而不是依赖手动运行时检查。

\n

FWIW,WWDC 2021\xe2\x80\x99s Swift 并发:更新示例应用程序Task { @MainActor in \xe2\x80\xa6 }中说明了从 GCD 到参与者隔离的方法和属性的演变。

\n
\n

然后你问:

\n
\n

然而,最近我意识到我忘记考虑一种情况 - 全局队列(或任何其他随机队列)上会发生什么?

\n
@SomeOtherActor\nfunc somethingElse() {\n    DispatchQueue.global().async {\n        // current actor is?\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

此时可能不言而喻,但对全局队列的调度并不是与任何参与者隔离的。

\n

现在,您可以用 a 替换对全局队列的调度Task.detached {\xe2\x80\xa6},或者从 Swift 5.7 开始(由于SE-0338),您可以使用一个非隔离async函数来将其从当前参与者中获取,如果这是您的意图。例如:

\n
@SomeOtherActor\nfunc somethingElse() async {\n    await somethingThatDoesNotRequireActorIsolation()\n}\n\nnonisolated func somethingThatDoesNotRequireActorIsolation() async {\n    \xe2\x80\xa6\n}\n
Run Code Online (Sandbox Code Playgroud)\n