There is a data storage for reading and writing data to a dictionary. I'm using a global actor that guarantees serial operation execution.
Code example:
@globalActor
struct DataStorageActor {
actor ActorType { }
static let shared: ActorType = ActorType()
}
@DataStorageActor
class CustomDataStorage {
private var storage: [String: String] = [:]
func write(key: String, value: String) async throws {
print("\(Date()): Write started")
try await Task.sleep(nanoseconds: 5_000_000_000)
storage[key] = value
print("\(Date()): Write completed for key: \(key)")
}
func read(key: String) async throws -> String? {
print("\(Date()): Read started")
let value = storage[key]
print("\(Date()): Read completed for key: \(key)")
return value
}
}
And a unit test was written:
@Test(arguments: ["Very long string"])
func testSerialExecution(input: String) async throws {
let storage = CustomDataStorage()
let task1 = Task {
try await storage.write(key: "test", value: input)
}
let task2 = Task {
try await Task.sleep(nanoseconds: 2_000_000_000)
let value = try await storage.read(key: "test")
print("\(Date()): Received value: \(value ?? "nil")")
return value
}
print("wait task 1")
_ = try await task1.value
print("wait task 2")
let value: String? = try await task2.value
print("check result")
#expect(value == input)
}
But the read operation runs in parallel, logs:
wait task 1
2025-04-07 12:43:03 +0000: Write started
2025-04-07 12:43:05 +0000: Read started
2025-04-07 12:43:05 +0000: Read completed for key: test
2025-04-07 12:43:05 +0000: Received value: nil
2025-04-07 12:43:08 +0000: Write completed for key: test
wait task 2
check result
Why does it work like that?
This is because during try await Task.sleep(nanoseconds: 5_000_000_000)
, or rather, every time an await
happens, the actor becomes free to do other tasks. In other words, actors are reentrant.
In your real code, if you are not await
ing for anything in write
, the state of the storage will be protected by the actor.
If you do await
something in write
, then other code can run on the actor during that await
. For solutions to this, see How to prevent actor reentrancy resulting in duplicative requests?