Problema de Data Race con Swift Actor
Estoy en proceso de convertir mi código para usar concurrencia en Swift y me encuentro con un problema con Actor que no sé cómo solucionar correctamente.
Aquí tienes un actor simple:
actor MyActor {
private var count = 0
func increase() {
count += 1
}
}
En otros lugares donde necesito actualizar el actor, tengo que llamar a sus funciones en un contexto de concurrencia:
Task {
await myActor.increase()
}
Eso está bien. Pero lo que no entiendo es si el actor devuelve la función increase
como un cierre de esta manera:
actor MyActor {
private var count = 0
func increase() -> () -> Void {
{
print("START")
self.count += 1
print("END")
}
}
}
En otros lugares, puedo obtener una referencia al cierre devuelto y llamarlo libremente en un contexto sin concurrencia:
class ViewController: UIViewController {
let actor = MyActor()
var increase: (() -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
Task {
increase = await actor.increase()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
let increase = self.increase
DispatchQueue.concurrentPerform(iterations: 100) { _ in
increase?()
}
}
}
}
El código anterior imprime lo siguiente en la salida:
START
START
START
START
START
END
END
START
START
...
No estoy seguro si entiendo o uso Actor correctamente. Actor protege su estado de las carreras de datos, pero en este caso, no lo impide. ¿Es este un comportamiento correcto? ¿Hay alguna forma de solucionarlo?
davy.ai
El comportamiento observado en el código dado es esperado y correcto.
En el primer ejemplo, la función
increase
es una función asíncrona que se ejecuta en un contexto de concurrencia utilizando la palabra claveawait
. El actor garantiza que el estado de la variablecount
está protegido contra condiciones de carrera mientras se ejecuta esta función.En el segundo ejemplo, la función
increase
devuelve un cierre que se puede ejecutar más tarde en un contexto no concurrente. Sin embargo, dado que el cierre no se ejecuta en un contexto de concurrencia, el actor no puede garantizar que la variablecount
esté protegida contra condiciones de carrera.Para solucionar esto, podemos modificar la función
increase
del actor para que acepte un bloque de finalización que se ejecute en un contexto de concurrencia. Aquí tienes un ejemplo:Ahora, cuando llamamos a la función
increase
, pasamos un bloque de finalización que se ejecuta en un contexto de concurrencia:Esto asegura que la variable
count
esté protegida contra condiciones de carrera incluso cuando se utiliza el cierre devuelto por la funciónincrease
.Ahora, la variable
count
está protegida contra condiciones de carrera incluso cuando se utiliza el cierre en un contexto no concurrente.