概述
Actor 的方法和属性与 Actor 是隔离的。SE-0313 改进了对actor隔离的控制通过使用提案中添加的isolated
/ nonisolated
关键字,可以将不是参与者成员的进程隔离到参与者,反之亦然,将参与者成员与参与者隔离。本文根据提案总结了如何使用这些关键字。
文中的操作验证是使用 Xcode 14 Beta 5 完成的。
isolated
关键词
作为演员的一个例子,请考虑下面的Counter
。
actor Counter {
var count: Int = 0
}
假设我们想在外部为这个演员增加count
。与参与者隔离的状态不能直接从外部操作以防止数据竞争,因此像以下这样的实现将是错误的。
func incrementCounter(_ counter: Counter) {
counter.count += 1 // ❗️ Actor-isolated property 'count' can not be mutated from a non-isolated context
}
在这里,我们可以通过将isolated
关键字附加到参数来将incrementCounter
函数与counter
演员隔离开来。这允许我们同步操作actor的属性count
。
func incrementCounter(_ counter: isolated Counter) {
counter.count += 1 // ✅ OK
}
这个incrementCounter
不是async
函数,但由于它与actor 隔离,所以在actor 外部调用它时需要await
。由于它与actor隔离,因此它具有与actor的方法相同的属性。
@main
public struct Main {
public static func main() async throws {
let counter = Counter()
await incrementCounter(counter)
}
}
isolated
相关错误
isolated
关键字只能附加到演员。由于函数的性质与标记为isolated
的参数隔离,因此用isolated
标记非参与者参数是没有意义的。
// ❗️ 'isolated' parameter has non-actor type 'Int'
func incrementCounter(_ counter: Counter, by value: isolated Int) {}
此外,isolated
不能附加到一个函数中的多个参数。这是因为函数不能被多个参与者隔离。
// ❗️エラー
func incrementCounters(_ counter1: isolated Counter, _ counter2: isolated Counter) {}
出于同样的原因,您也不能在 actor 方法上使用 isolated
关键字。有方法的演员
extension Counter {
// ❗️エラー
func transferCount(to other: isolated Counter) {}
}
但是,从 Xcode 14 Beta 5 开始,这两个代码都可以在没有编译错误或警告的情况下运行,并且如果您尝试,实际上可能会引入数据竞争。这个问题将在适当的时候修复,上面的代码应该不再运行。
参考:https://github.com/apple/swift/issues/60474
何时使用isolated
isolated
至于关键字,我想不出绝对必须使用它的情况(如果有人知道,请在评论中告诉我)。这是因为如果你想要一个actor中的独立进程,你可以让它成为actor的一个方法。
但是,从代码管理和代码编写风格来看,很可能会出现将isolated
关键字作为全局函数而不是actor方法更合适的情况,所以值得记住.
nonisolated
关键字
isolated
我发现你可以使用isolated
关键字将actor 之外的函数与actor 隔离。通过使用nonisolated
关键字,您可以反过来不隔离演员的成员。
例如,如下图在actor方法中添加nonisolated
,就不会被隔离,可以从actor外部同步调用。
extension Counter {
func f1() {}
nonisolated func f2() {}
}
@main
public struct Main {
public static func main() async throws {
let counter = Counter()
counter.f1() // ❗️ Expression is 'async' but is not marked with 'await'
counter.f2() // ✅ OK
}
}
将nonisolated
关键字添加到var
属性会导致错误。首先,一个actor是为了保护可变状态,所以我认为var
之所以不被隔离,是为了偏离actor的使用。
actor Counter {
nonisolated let id: String
nonisolated var count: Int = 0 // ❗️ 'nonisolated' can not be applied to stored properties
nonisolated var p1: String { "p1" }
nonisolated func f1() {}
}
此外,let
属性可能是 nonisolated
,但恐怕实际上这样做并没有太大的好处。这是因为let
默认情况下可以在actor外部同步访问。这是因为为了发生数据竞争,必须同时访问相同的数据,其中一个必须是写入。我认为。
actor Counter {
let id: String
init(id: String) {
self.id = id
}
}
@main
public struct Main {
public static func main() async throws {
let counter = Counter(id: "counter")
print(counter.id) // ✅ OK。 nonisolated がついていなくても同期的にアクセスできる
}
}
何时使用nonisolated
nonisolated
有用的一个例子是允许同步访问actor属性,这些属性可以安全地从多个线程同时访问。
考虑以下description
属性,它将Counter
的摘要作为字符串返回。
actor Counter {
let id: String
var count: Int = 0
var description: String {
"Counter with ID \(id)"
}
init(id: String) {
self.id = id
}
}
description
该属性与actor 隔离,因此必须从actor 外部使用await
调用它。这很不方便,因为它不能直接从同步函数中调用。
@main
public struct Main {
public static func main() async throws {
let counter = Counter(id: "counter")
print(counter.description) // ❗️ Expression is 'async' but is not marked with 'await'
}
}
但是,当我冷静地回顾description
的实现时,它只依赖于id
,即let
属性。如前所述,let
属性不会引起数据竞争,可以从多个线程同时访问,所以description
也可以从多个线程同时访问,不需要actor保护。就是这个意思.也就是说description
不需要隔离到某个actor,但是因为是隔离的,所以只能异步访问,不方便。在这种情况下,将nonisolated
添加到description
以防止actor被隔离,使得同步访问成为可能。
actor Counter {
let id: String
var count: Int = 0
nonisolated var description: String {
"Counter with ID \(id)"
}
}
@main
public struct Main {
public static func main() async throws {
let counter = Counter(id: "counter")
print(counter.description) // ✅ OK
}
}
当然,在这里,如果nonisolated
进程可以访问一个应该与参与者隔离的属性,则会导致数据竞争。我们可以安全地使用nonisolated
,因为编译器会对这种危险的访问发出错误。
actor Counter {
let id: String
var count: Int = 0
nonisolated var description: String {
// ❗️ Actor-isolated property 'count' can not be referenced from a non-isolated context
"Counter with ID \(id) with count \(count)"
}
}
nonisolated
有用的另一个地方是当您希望参与者符合特定协议时。例如,Counter
CustomStringConvertible
我希望它符合CustomStringConvertible
的请求是
public protocol CustomStringConvertible {
var description: String { get }
}
但是,像下面这样的Counter
的实现并不能很好地符合并导致错误。
actor Counter: CustomStringConvertible {
let id: String
// ❗️ Actor-isolated property 'description' cannot be used to satisfy nonisolated protocol requirement
var description: String {
"Counter with ID \(id)"
}
}
这是因为description
是隔离的,必须从actor外部异步调用,所以接口有async
属性。由于假设description
可以作为CustomStringConvertible
的请求被同步访问,所以只能异步访问这个请求是无法满足的。nonisolated
在这种情况下很有效。将nonisolated
添加到description
可以从actor 外部进行同步访问,因此可以满足对CustomStringConvertible
的请求。
// ✅ OK
actor Counter: CustomStringConvertible {
let id: String
nonisolated var description: String {
"Counter with ID \(id)"
}
}
除了CustomStringConvertible
,Swift 的大部分内置协议如Identifiable
和Hashable
都需要同步属性和方法,所以nonisolated
经常用在actor 符合某种协议的情况下,会有很多。
概括
-
isolated
您可以通过将isolated
关键字附加到函数参数中的参与者来将函数隔离到参与者。 -
将
nonisolated
关键字添加到参与者的成员中使其非隔离- 当您想从参与者外部同步访问,或者当您希望参与者遵守特定协议时很有用
参考
原创声明:本文系作者授权爱码网发表,未经许可,不得转载;
原文地址:https://www.likecs.com/show-308623646.html