es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

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?

Tags:  ,

Answer

  1. Avatar for 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 clave await. El actor garantiza que el estado de la variable count 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 variable count 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:

    actor MyActor {
        private var count = 0
    
        func increase(completion: @escaping () -> Void) {
            Task {
                print("START")
                self.count += 1
                print("END")
                completion()
            }
        }
    }
    

    Ahora, cuando llamamos a la función increase, pasamos un bloque de finalización que se ejecuta en un contexto de concurrencia:

    Task {
        await myActor.increase {
            // bloque de finalización ejecutado 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ón increase.

    class ViewController: UIViewController {
        let actor = MyActor()
    
        var increase: (() -> Void)?
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            Task {
                await actor.increase {
                    self.increase = {
                        // cierre ejecutado en un contexto de concurrencia
                        self.actor.increase {
                            // bloque de finalización ejecutado en un contexto de concurrencia
                        }
                    }
                }
            }
    
            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                let increase = self.increase
    
                DispatchQueue.concurrentPerform(iterations: 100) { _ in
                    increase?()
                }
            }
        }
    }
    

    Ahora, la variable count está protegida contra condiciones de carrera incluso cuando se utiliza el cierre en un contexto no concurrente.

Comments are closed.