如何在异步方法中实现线程安全?

3
我试图读取钥匙串,但文档警告我如下:
SecItemCopyMatching 会阻塞调用线程,因此如果从主线程调用它,可能会导致应用程序的 UI 假死。相反,应该从后台调度队列或异步函数中调用 SecItemCopyMatching。 来源 因此,我想编写一个在后台运行的异步方法。
actor Keychain {
    public static let standard = Keychain()
    
    public enum Error: Swift.Error {
        case failed(String)
    }
    
    public func get(_ key: String) async throws -> Data? {
        let backgroundTask = Task(priority: .background) {
            var query: [String: Any] = [
                type(of: self).klass       : kSecClassGenericPassword,
                type(of: self).attrAccount : key,
                type(of: self).matchLimit  : kSecMatchLimitOne,
                type(of: self).returnData  : kCFBooleanTrue as CFBoolean
            ]
            
            var item: CFTypeRef?
            let status = SecItemCopyMatching(query as CFDictionary, &item)
            
            guard status == errSecSuccess else {
                if let errorMessage = SecCopyErrorMessageString(status, nil) {
                    throw Error.failed(String(errorMessage))
                } else {
                    throw Error.failed("unsupported")
                }
            }
            
            return item as? Data
        }
        return try await backgroundTask.value
    }
}

我的问题是,这位演员已经将其设为线程安全了吗?
通常我会添加一个 NSLock 来保证安全。
public func get(_ key: String) async throws -> Data? {
    lock.lock()
    defer { lock.unlock() }
    
    (...)
    
    return try await task.value
}

然而,现在我收到一个警告:Instance method 'lock' is unavailable from asynchronous contexts; Use async-safe scoped locking instead; this is an error in Swift 6
那么,我该如何解决这个问题?
3个回答

4

是的,使用actor可以使其线程安全,无需使用锁等。请参阅WWDC 2021视频使用Swift actor保护可变状态


顺便说一下,由于你已将其放到了actor上,因此不需要将get(_:)方法设置为async。actor已经在后台线程上运行。因此,请删除async限定符,然后删除Task

actor Keychain {
    ...
    
    public func get(_ key: String) throws -> Data? {
        var query: [String: Any] = [
            type(of: self).klass       : kSecClassGenericPassword,
            type(of: self).attrAccount : key,
            type(of: self).matchLimit  : kSecMatchLimitOne,
            type(of: self).returnData  : kCFBooleanTrue as CFBoolean
        ]
        
        var item: CFTypeRef?
        let status = SecItemCopyMatching(query as CFDictionary, &item)
        
        guard status == errSecSuccess else {
            if let errorMessage = SecCopyErrorMessageString(status, nil) {
                throw Error.failed(String(errorMessage))
            } else {
                throw Error.failed("unsupported")
            }
        }
        
        return item as? Data
    }
}

0

看起来你正在使用所有的本地变量(没有实例或静态变量),所以应该没问题。


0

为什么要把不必要的Keychain操作放在异步块中?没有它也可以。Keychain操作是线程安全的,这是由苹果保证的,请参阅此文档。Apple

因此,不需要异步块就可以完成操作,并将所有UI更改放在主线程中(如果尚未在主线程中)。UI更改需要在主线程中进行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接