1
votes

S'assurer que la logique d'exécution de deux minuteries ne s'exécute pas en même temps

J'ai deux minuteries qui s'exécutent à des intervalles de temps différents, dont l'un est ajouté à une boucle d'exécution.

let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
    // do some stuff
}

let timer2 = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
    // do some other stuff
}
RunLoop.current.add(timer2, forMode: RunLoop.Mode.common)

J'ai une condition qui nécessite que la logique d'exécution des deux minuteries ne fonctionne pas en même temps puisqu'ils modifient tous les deux les mêmes structures de données.

Est-ce déjà garanti par iOS? Sinon, quelle est la meilleure façon de m'en assurer?


1 commentaires

Comment avez-vous ajouté le premier minuteur?


4 Réponses :


0
votes

Si vous planifiez les deux minuteries, rien ne garantit qu'elles seront déclenchées en même temps.

Vous pouvez vous en assurer en programmant la minuterie2 dans le bloc de feu de la minuterie1, etc.


0 commentaires

2
votes

Tant que vous programmez les minuteries sur le même RunLoop , vous êtes en sécurité. Un RunLoop est toujours associé à exactement un Thread et donc les blocs seront exécutés sur ce Thread. Par conséquent, ils ne tireront jamais exactement au même moment. Les choses seront différentes, cependant, si vous les planifiez sur deux instances RunLoop différentes. Je ne suis pas sûr de ce que vous voulez dire lorsque vous dites qu'un seul minuteur est ajouté à un RunLoop . Si vous n'ajoutez pas de minuterie à un RunLoop , il ne se déclenchera pas du tout.


3 commentaires

Les deux sont ajoutés aux runloops, le premier est implicitement ajouté à .default et le second est ajouté à .common . Si vous n'ajoutez pas de minuterie à une boucle d'exécution, elle se déclenchera car elle a été implicitement ajoutée à .default


Je comprends que les minuteries sont ajoutées implicitement, d'où ma réponse. Ce que vous appelez .default , .common , cependant, ne sont pas des instances de RunLoop différentes. Ce sont des modes!


Mon erreur, j'ai mal compris, merci d'avoir clarifié.



0
votes

Si vous devez assurer l'exécution en série des blocs de minuterie quel que soit l'endroit où les minuteries elles-mêmes ont été programmées, vous devez créer votre propre file d'attente en série et répartir le travail de votre minuterie dans celle-ci:

let timerQueue = DispatchQueue(label: "timerQueue")
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
    timerQueue.async {
        // do stuff
    }
}

let timer2 = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
    timerQueue.async {
        // do some other stuff
    }
}


0 commentaires

2
votes

Votre code ne correspond pas à ce que vous décrivez: l'API SchedulerTimer (withTimeInterval ajoute implicitement le minuteur à la boucle d'exécution. Vous ne devez pas l'ajouter vous-même à la boucle d'exécution. Donc, les deux strong > les minuteries fonctionnent sur la boucle d'exécution.


Je recommande d'utiliser DispatchSourceTimer et une DispatchQueue personnalisée. La file d'attente est en série par défaut et fonctionne comme FIFO (d'abord dans le premier sorti). La file d'attente série garantit que les tâches ne sont pas exécutées en même temps.

Les minuteries sont suspendues après leur création. Vous devez appeler activate () ( ou resume () sous iOS 9 et inférieur) pour démarrer les minuteries par exemple dans viewDidLoad

class Controller : UIViewController {
    
    let queue = DispatchQueue(label: "myQueue")
    
    lazy var timer1 : DispatchSourceTimer = {
        let timer = DispatchSource.makeTimerSource(queue: queue)
        timer.schedule(deadline:.now() + 1.0, repeating: 1.0)
        timer.setEventHandler(handler: eventHandler1)
        return timer
    }()
    
    
    lazy var timer2 : DispatchSourceTimer = {
        let timer = DispatchSource.makeTimerSource(queue: queue)
        timer.schedule(deadline:.now() + 0.5, repeating: 0.5)
        timer.setEventHandler(handler: eventHandler2)
        return timer
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        timer1.activate()
        timer2.activate()
    }
    
    func eventHandler1() {
        DispatchQueue.main.async {
           // do stuff on the main thread
        }
    }
    
    func eventHandler2() {
        DispatchQueue.main.async {
           // do stuff on the main thread
        }
    }
}

Les propriétés de la minuterie sont déclarées paresseusement pour éviter les options et les fermetures du gestionnaire d'événements ( () -> Void ) sont implémentées en tant que méthodes standard.


11 commentaires

re "Vous ne devez pas ajouter vous-même la minuterie au runloop.", la seule raison pour laquelle je l'ai fait est que je voulais que cette logique s'exécute lorsque les gens font défiler l'application, alors je l'ai ajoutée au .common runloop hackingwithswift.com/articles/117/the-ultimate -guide-à-temps‌ r


Veuillez lire attentivement l'article lié. Si vous avez besoin de contrôler la boucle d'exécution, vous devez utiliser l'API non planifiée ( Timer (timeInterval: ... )


Bonne prise, j'ai raté ça.


Néanmoins, DispatchSourceTimer est plus précis, plus fiable et plus indépendant


Gotcha - maintenant je rencontre l'erreur ... doit être utilisée à partir du thread principal uniquement , y a-t-il un moyen de contourner cela en utilisant votre solution?


UITableView.indexPathsForVisibleRows doit être utilisé à partir du thread principal uniquement et UIScrollView.contentOffset doit être utilisé à partir du thread principal uniquement


Je vois, vous devez exécuter des éléments liés à l'interface utilisateur dans les gestionnaires d'événements sur le thread principal. S'il vous plaît voir la modification


Merci beaucoup. Annuler la minuterie est le meilleur moyen timer1.cancel () ? De plus, maintenant que les gestionnaires sont asynchrones, cela affecte-t-il les tâches qui ne s'exécutent pas en même temps?


Ça dépend. Si vous voulez juste arrêter un chronomètre une fois, annuler convient. Mais si vous voulez démarrer et arrêter les minuteries de manière dynamique, déclarez les propriétés facultatives et ajoutez une logique pour contrôler le démarrage et l'arrêt des minuteries de manière fiable.


Gotcha merci. J'ai également modifié le commentaire après l'avoir publié, mais maintenant que les gestionnaires sont asynchrones, cela affecte-t-il les tâches qui ne s'exécutent pas en même temps?


La file d'attente principale est également en série.