Swift,actor:Actor 隔离属性“扫描”无法从非隔离上下文中进行变异

问题描述 投票:0回答:3

我有一个演员:

actor StatesActor {

    var job1sActive:Bool = false
    ...

}

我有一个使用该演员的对象:

class MyObj {
    
    let myStates = StatesActor()
    
    func job1() async {
    
        myStates.job1IsActive = true

    }
}

线路:

myStates.job1IsActive = true

出现此错误:

Actor 隔离属性“job1IsActive”无法从非隔离上下文中发生突变

如何使用 Actor 正确存储/读取状态信息,以便 MyObj 可以使用它来读取和设置状态?

swift actor swift-concurrency
3个回答
44
投票

如何使用 Actor 正确存储/读取状态信息,以便 MyObj 可以使用它来读取和设置状态?

您无法从 Actor 外部更改 Actor 的实例变量。这就是演员的全部意义!

相反,给演员一个方法来设置它的自己的实例变量。然后您就可以调用该方法(使用

await
)。


6
投票

不允许使用未在参与者上运行的代码中的属性设置器(“参与者隔离代码”是确切的术语)。改变 Actor 状态的最佳位置是 Actor 本身内部的代码。对于你的情况:

actor StatesActor {
    var job1IsActive: Bool = false
    func startJob1() {
        job1IsActive = true
        ...
    }
}
class MyObj {
    let myStates = StatesActor()
    func job1() async {
        await myStates.startJob1()
    }
}

异步属性设置器在理论上是可行的,但不安全。这可能就是 Swift 没有它们的原因。编写像

if await actor.a == nil { await actor.a = "Something" }
这样的东西会变得太容易了,其中
actor.a
可以在对 getter 和 setter 的调用之间改变。

上面的答案在 99% 的情况下都有效(对于所提出的问题来说已经足够了)。但是,在某些情况下,需要从 Actor 外部的代码更改 Actor 的状态。对于

MainActor
,请使用
MainActor.run(body:)
。对于全局参与者,
@NameOfActor
可以应用于函数(并且可以用于定义类似于
run
MainActor
函数)。在其他情况下,可以在函数的 actor 参数前面使用
isolated
关键字:

func job1() async {
    await myStates.startJob1()
    let update: (isolated StatesActor) -> Void = { states in
        states.job1IsActive = true
    }
    await update(myStates)
}

0
投票

您可以使用更通用的方法,允许您在参与者隔离级别的一次调用中以任何您想要的方式修改属性。

actor StatesActor {
  var job1IsActive: Bool = false
  
  // generic method to modify job1IsActive and return a result if required
  func job1Isolated<T: Sendable>(_ closure: (inout Bool) -> T) -> T {
    return closure(&job1IsActive)
  }
}
class MyObj {
    let myStates = StatesActor()
    func job1() async {
      await myStates.job1Isolated { 
        // this call will be done on actor isolated level
        $0 = false 
      }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.