我的基于 Actor 的商店真的线程安全吗?

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

我试图了解我的代码是否真的是线程安全的。对

storeData
的读/写应该是,但是
sync
中对
didSet
的调用也是线程安全的吗?

另外,是否有更好的做法来在参与者中加载初始数据?在构造函数中初始化

Task
感觉很奇怪。

actor Store<Element: Codable>: StoreType {
    private let fileName: String
    private var storeData: [Element]? {
        didSet {
            sync()
        }
    }
    
    init(fileName: String) {
        self.fileName = fileName
        
        Task {
            await loadFromPlist()
        }
    }
    
    func read() async throws -> [Element]? {
        storeData
    }
    
    func write(_ value: [Element]) async throws {
        storeData = value
    }
    
    func remove() async throws {
        storeData = nil
    }
}

private extension Store {
    func loadFromPlist() {
        let decoder = PropertyListDecoder()
        
        guard let data = try? Data.init(contentsOf: plistURL),
              let elements = try? decoder.decode([Element].self, from: data) else {
            return
        }
        
        storeData = elements
    }
    
    private func sync() {
        let encoder = PropertyListEncoder()
        guard let data = try? encoder.encode(storeData) else {
            return
        }
        
        if FileManager.default.fileExists(atPath: plistURL.path) {
            try? data.write(to: plistURL)
        } else {
            FileManager.default.createFile(atPath: plistURL.path, contents: data, attributes: nil)
        }
    }
    
    var plistURL: URL {
        let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        return documents.appendingPathComponent("\(fileName).plist")
    }
}
swift thread-safety actor
1个回答
0
投票

这段代码是“线程安全的”,因为在每一点,

storeData
都是一个有效的 Swift 值。您永远不会因低级数据竞争而崩溃。读取
storeData
将始终返回一个包含“某物”的数组。并且此代码永远不会损坏 plist 文件。

这段代码不是“线程安全的”,因为在初始化过程中肯定会出现令人惊讶的情况。

let store = Store<String>(fileName: "x")
let oldValues = try await store.read()
// What is oldValues? Might be empty. Might be the contents of the file.

不保证您的

init
任务将在
read
之前或之后运行。所以你不知道那里会发生什么。

由于读取文件本质上是一个可能会失败的异步事件,因此我建议使用异步和抛出标记您的

init
。那么你就不需要任务了,一切都有意义了。并且没有理由使
read()
异步或抛出。

或者,您可以延迟加载文件,直到

read()
,当前是异步的并会抛出异常。我会使用Optional来跟踪数据是否已加载,而不是在这里返回Optional。

© www.soinside.com 2019 - 2024. All rights reserved.