@globalActor
actor LibraryAccount {
static let shared = LibraryAccount()
var booksOnLoan: [Book] = [Book()]
func getBook() -> Book {
return booksOnLoan[0]
}
}
class Book {
var title: String = "ABC"
}
func test() async {
let handle = Task { @LibraryAccount in
let b = await LibraryAccount.shared.getBook() // WARNING and even ERROR without await (although function is not async)
print(b.title)
}
}
此代码生成以下警告:
调用执行元隔离示例方法“getBook()”返回的不可发送类型“Book”不能跨越执行元边界
然而,闭包本身被标记为相同的全局Actor,这里不应该有Actor边界。有趣的是,当从出错的行中删除wait时,它会发出一个错误:
表达式为“async”,但未标记为“await”
这看起来像是它没有识别出这是与保护闭包的Actor的同一个示例。
这是怎么回事?是我误解了GlobalActors的工作原理还是这是一个bug?
1条答案
按热度按时间c9qzyr3d1#
全球参与者并不是真的被设计成这样使用的。
标记
@globalActor
的类型只是一个 * 标记类型 ,它提供了一个shared
属性,该属性返回执行同步的实际参与者示例。正如提案所言:
全局参与者类型可以是struct、enum、actor或final类。它本质上只是一个标记类型,通过
shared
提供对实际共享参与者示例的访问。共享示例是全局唯一的参与者示例,它与全局参与者类型同义,并将用于同步对使用全局参与者注解的任何代码或数据的访问。因此,你在
static let shared =
后面写的东西不一定是LibraryAccount()
,它可以是世界上任何一个参与者,标记为@globalActor
的类型甚至不需要是参与者本身。因此,从Swift的Angular 来看,
LibraryAccount
全局参与者与您在代码中编写的任何参与者类型的表达式(如LibraryAccount.shared
)完全不是同一个参与者。您可能已经实现了shared
,因此当您第二次调用它时,它返回LibraryAccount
的不同示例,谁知道呢?静态分析也就到此为止了。Swift所知道的是,两个标记为
@LibraryAccount
的东西是孤立于同一个参与者的--也就是说,这纯粹是 nominal。毕竟,全局参与者最初的动机是为了轻松地将需要在主线程上运行的东西标记为@MainActor
。全局参与者的主要动机是主参与者, 并且此功能的语义根据主线程执行的需要进行了调整 *。我们抽象地知道,还有其他类似的用例,但全局参与者可能不适合这些用例。
您应该创建一个“标记”类型,其中几乎没有任何内容--这就是全局参与者。
在本例中,您可以将
LibraryAccount
重命名为LibraryAccountActor
,然后将属性和方法移动到LibraryAccount
* 类 * 中,并标记为@LibraryAccountActor
: