Tengo un problema con el bloque de notificación de DispatchGroup que se llama al principio de mi aplicación e hice este ejemplo de patio de recreo para experimentar. Según la salida, a veces se llama incluso antes del primer .leave(). Siento que me estoy perdiendo algo obvio y ahora lo he mirado demasiado tiempo.
let s = DispatchSemaphore(value: 1) let dg = DispatchGroup() func go() -> Void { for i in 1...2 { doWork(attemptNo: i, who: "Lily", secs: Double.random(in: 1.0...5.0)) doWork(attemptNo: i, who: "Emmie", secs: Double.random(in: 1.0...10.0)) doWork(attemptNo: i, who: "Wiley", secs: Double.random(in: 1.0...3.0)) } } func doWork(attemptNo: Int, who: String, secs: TimeInterval) -> Void { DispatchQueue.global().async { dg.enter() print("\(who) wants to work, will wait \(secs) seconds, attempt #\(attemptNo)") if s.wait(timeout: .now() + secs) == .timedOut { print("\(who) was denied. No soup for me! Task #\(attemptNo) not going to happen.") dg.leave() return } let workSecs = UInt32(Int.random(in: 1...3)) print("\(who) went to work for \(workSecs) seconds on task #\(attemptNo)") DispatchQueue.global().asyncAfter(deadline: .now() + TimeInterval(workSecs)) { print("\(who) is sliding down the dinosaur tail. Task #\(attemptNo) all done!") s.signal() dg.leave() } } } go() dg.notify(queue: .global(), execute: {print("Everyone is done.")})Salida de muestra:
Emmie wants to work, will wait 4.674405654828654 seconds, attempt #1 Lily wants to work, will wait 1.5898288206500877 seconds, attempt #1 Wiley wants to work, will wait 1.2182416407288 seconds, attempt #1 Lily wants to work, will wait 3.3225083978280647 seconds, attempt #2 Everyone is done. Wiley wants to work, will wait 2.801577828588925 seconds, attempt #2 Emmie wants to work, will wait 8.9696422949966 seconds, attempt #2 Lily went to work for 3 seconds on task #2 Wiley was denied. No soup for me! Task #1 not going to happen. Lily was denied. No soup for me! Task #1 not going to happen. Wiley was denied. No soup for me! Task #2 not going to happen. Lily is sliding down the dinosaur tail. Task #2 all done! Emmie went to work for 3 seconds on task #1 Emmie is sliding down the dinosaur tail. Task #1 all done! Emmie went to work for 2 seconds on task #2 Emmie is sliding down the dinosaur tail. Task #2 all done!En este caso, "todos han terminado" ocurre casi de inmediato y, dado que .leave son junto con no obtener el semáforo o después de que se realiza el "trabajo", esto no tiene sentido. Ayuda por favor.
Su principal problema es que llama a dg.enter() en el lugar equivocado. Siempre desea llamar a enter() antes de la llamada asíncrona y desea llamar a leave() cuando finaliza la llamada asíncrona.
Como su código está escrito ahora, el ciclo for finaliza antes de que se realice la primera llamada para enter , por lo que la notify se activa de inmediato.
func doWork(attemptNo: Int, who: String, secs: TimeInterval) -> Void { dg.enter() DispatchQueue.global().async { print("\(who) wants to work, will wait \(secs) seconds, attempt #\(attemptNo)") if s.wait(timeout: .now() + secs) == .timedOut { print("\(who) was denied. No soup for me! Task #\(attemptNo) not going to happen.") dg.leave() return } let workSecs = UInt32(Int.random(in: 1...3)) print("\(who) went to work for \(workSecs) seconds on task #\(attemptNo)") sleep(workSecs) print("\(who) is sliding down the dinosaur tail. Task #\(attemptNo) all done!") s.signal() dg.leave() } }Su código también hace un uso extraño de un semáforo y el sueño. Supongo que este es un intento de simular un proceso en segundo plano de larga ejecución.
Así de sencillo, doWork vuelve antes de entrar en el grupo...
esto debería funcionar, como se esperaba
import Foundation let s = DispatchSemaphore(value: 1) let dg = DispatchGroup() func go() -> Void { for i in 1...2 { doWork(attemptNo: i, who: "Lily", secs: Double.random(in: 1.0...5.0)) doWork(attemptNo: i, who: "Emmie", secs: Double.random(in: 1.0...10.0)) doWork(attemptNo: i, who: "Wiley", secs: Double.random(in: 1.0...3.0)) } } func doWork(attemptNo: Int, who: String, secs: TimeInterval) -> Void { dg.enter() DispatchQueue.global().async { //dg.enter() print("\(who) wants to work, will wait \(secs) seconds, attempt #\(attemptNo)") if s.wait(timeout: .now() + secs) == .timedOut { print("\(who) was denied. No soup for me! Task #\(attemptNo) not going to happen.") dg.leave() return } let workSecs = UInt32(Int.random(in: 1...3)) print("\(who) went to work for \(workSecs) seconds on task #\(attemptNo)") sleep(workSecs) print("\(who) is sliding down the dinosaur tail. Task #\(attemptNo) all done!") s.signal() dg.leave() } } go() dg.notify(queue: .global(), execute: {print("Everyone is done.")})La mejor manera de evitar tal error es usar la API adecuada
func doWork(attemptNo: Int, who: String, secs: TimeInterval) -> Void { DispatchQueue.global().async(group: dg) { print("\(who) wants to work, will wait \(secs) seconds, attempt #\(attemptNo)") if s.wait(timeout: .now() + secs) == .timedOut { print("\(who) was denied. No soup for me! Task #\(attemptNo) not going to happen.") return } let workSecs = UInt32(Int.random(in: 1...3)) print("\(who) went to work for \(workSecs) seconds on task #\(attemptNo)") sleep(workSecs) print("\(who) is sliding down the dinosaur tail. Task #\(attemptNo) all done!") s.signal() } }