Há um armazenamento de dados para leitura e gravação de dados em um dicionário. Estou usando um ator global que garante a execução de operações em série.
Exemplo de código:
@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
}
}
E um teste unitário foi escrito:
@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)
}
- Em um thread separado, eu lanço uma longa operação de gravação (5 segundos)
- Em um thread separado, eu lanço uma operação de leitura com um atraso de 2 segundos
- Espero que a operação de leitura aguarde a conclusão da operação de gravação
Mas a operação de leitura é executada em paralelo, registra:
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
Por que funciona assim?
Isso ocorre porque durante
try await Task.sleep(nanoseconds: 5_000_000_000)
, ou melhor, sempre que umawait
acontece, o ator fica livre para realizar outras tarefas. Em outras palavras, os atores são reentrantes .No seu código real, se você não estiver
await
procurando por nada emwrite
, o estado do armazenamento será protegido pelo ator.Se você fizer
await
algo emwrite
, outro código poderá ser executado no ator durante esseawait
. Para soluções para isso, consulte Como evitar que a reentrada do ator resulte em solicitações duplicadas?