swift - 无法从标有相同 globalActor 的闭包安全地访问 globalActor 的方法

标签 swift swift-concurrency

@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)
    }
}

此代码生成以下警告:

Non-sendable type 'Book' returned by call to actor-isolated instance method 'getBook()' cannot cross actor boundary

但是,闭包本身标记有相同的全局Actor,这里不应该有Actor边界。有趣的是,当从有问题的行中删除等待时,它会发出错误:

Expression is 'async' but is not marked with 'await'

这看起来像是它无法识别出这是与守护闭包的 Actor 相同的实例。

这是怎么回事?我是否误解了 GlobalActors 的工作原理,或者这是一个错误?

最佳答案

全局参与者实际上并不是为了以这种方式使用而设计的。

您标记@globalActor的类型只是一个标记类型,提供一个共享属性,该属性返回实际的Actor实例同步。

作为proposal说:

A global actor type can be a struct, enum, actor, or final class. It is essentially just a marker type that provides access to the actual shared actor instance via shared. The shared instance is a globally-unique actor instance that becomes synonymous with the global actor type, and will be used for synchronizing access to any code or data that is annotated with the global actor.

因此,你在static let share =之后写的不一定是LibraryAccount(),它可以是世界上任何一个actor。标记为 @globalActor 的类型甚至不需要本身就是一个 actor。

因此,从 Swift 的角度来看,LibraryAccount 全局 actor 与您在代码中编写的任何 actor 类型表达式(例如 LibraryAccount.shared)是同一个 actor 并不明显>。您可能已经实现了 shared,以便在您第二次调用它时返回 LibraryAccount 的不同实例,谁知道呢?静态分析仅此而已。

Swift 确实知道的是,标记为 @LibraryAccount 的两个事物被隔离到同一个参与者 - 即,这纯粹是名义。毕竟,全局 Actor 的最初动机是轻松地用 @MainActor 标记需要在主线程上运行的事物。 Quote (强调我的):

The primary motivation for global actors is the main actor, and the semantics of this feature are tuned to the needs of main-thread execution. We know abstractly that there are other similar use cases, but it's possible that global actors aren't the right match for those use cases.

您应该创建一个“标记”类型,其中几乎没有任何内容 - 这就是全局参与者。并通过标记将您的业务逻辑与全局参与者隔离。

就您而言,您可以将 LibraryAccount 重命名为 LibraryAccountActor,然后将属性和方法移至 LibraryAccount ,用 @LibraryAccountActor 标记:

@globalActor
actor LibraryAccountActor {
    static let shared = LibraryAccountActor()
}

@LibraryAccountActor
class LibraryAccount {
    static let shared = LibraryAccount()
    var booksOnLoan: [Book] = [Book()]
    func getBook() async -> Book { // this doesn't need to be async, does it?
        return booksOnLoan[0]
    }
}

class Book {
    var title: String = "ABC"
}

func test() async {
    let handle = Task { @LibraryAccountActor in
        let b = await LibraryAccount.shared.getBook()
        print(b.title)
    }
}

关于swift - 无法从标有相同 globalActor 的闭包安全地访问 globalActor 的方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74172334/

相关文章:

ios - 如何在 SwiftUI 和 iOS 14 中向领先的导航栏项添加角标(Badge)?

ios - 如何正确消除这个并发编译错误?

Swift:UIPanGestureRecognizer 反向滑动错误

ios - 从不同的pickerView中获取值(value)

swift - 异步自动释放池

swift - 使用 Swift 并发和观察框架 iOS 17 监听搜索栏随时间变化的值

swift - 从无任务上下文同步访问 actor 属性

ios - 即使它是单个事件,我是否应该删除 Firebase 观察者?

swift - 如何在调用 `viewDidLoad` 之前加载 self.navigationController