swiftdispatch-queueswift-concurrency

Is it safe to call DispatchQueue sync for read operations?


I have a class that reads from and writes to a dictionary using a serial dispatch queue. While the write operations are asynchronous, the read operations are synchronous. I want to avoid the use of a completion handler for reading. Does this create any issues? The issue could range from deadlocks or crashes by calling from the main thread.

public final class UserRegistry {

    private var infoMap: [String: User] = [:]

    
    private let queue: DispatchQueue = .init(label: "userRegistry.queue")

    func register(_ user: User) {
        queue.async { [weak self] in
            self?.infoMap[user.id] = user
        }
    }

    func deregister(_ user: User) {
        queue.async { [weak self] in
            self?.infoMap[user.id] = nil
        }
    }

    func getUser(for id: String) -> User? {
        queue.sync {
            infoMap[id]
        }
    }
}

I tried to create a completion handler and that would cause a series of changes at the call site across the code base. I expect that reading synchrnously should be safe regardless of which thread the consumer calls this from.

Read operation - func getUser(for id: String) -> User?

Write operations - func register(_ user: User) & func deregister(_ user: User)


Solution

  • Yes, you can safely use sync for reads and async for writes and there will not be any deadlocks or races on infoMap.

    You don't even particularly need to use [weak self], since the blocks capturing self won't live long.

    You also don't probably need to use async for the writes. You could just use sync for writes too. Because infoMap is never copied, there won't be any copy-on-writes, and it will only be reallocated rarely. Thus the write operations should almost always be cheap enough to be run synchronously.

    Keep in mind that if User is a reference type (or otherwise doesn't have value semantics), you might still have races involving individual User objects.