[RESOLU] Comment faire pour qu'une action ne se déclenche qu'après la fin d'une autre

2»

Réponses

  • DrakenDraken Membre
    décembre 2017 modifié #32

    Reprenons les bases.


     


    Hypothése : j'ai besoin de faire un long calcul.



    var resultat = 0

    func traitementLong() {
    for n in 0...100000000 {
    resultat += n
    }
    }

    a


    Utilisation " classique " de la fonction :


    a




    resultat = 0
    print ("Début du calcul")
    traitementLong()
    print ("Fin des opérations")
    print ("Résultat du calcul : ", resultat)

    a


     



     


    Début du calcul


    Fin des opérations


    Résultat du calcul :  5000000050000000



     


    Cette manière de procéder a un défaut : l'exécution de l'application mobilise toute la puissance du CPU. Tout est bloqué pendant le calcul. L'interface utilisateur est figée. Les contrôles graphiques ne fonctionnent plus, etc ..


     


    Si le calcul prend une seconde, tout est figée pendant ce temps. C'est le défaut de l'informatique classique où les opérations se font les unes après les autres, de manière séquentielle (synchrone).

  • Pour éviter les blocages du système, il faut exécuter le calcul dans un processus secondaire, de manière asynchrone. Comme ça :


    a



    resultat = 0
    print ("Début du calcul")

    // Création d'un processus secondaire en tâche de fond
    DispatchQueue.global().async {
    self.traitementLong()
    }

    print ("Fin des opérations")


    a


    L'exécution semble ultra-rapide. L'application affiche tout de suite " Début du calcul " et " Fin des opérations ". L'interface n'est pas figée, les contrôles graphiques sont réactifs. En fait la fonction traitementLong() continue de s'exécuter dans un autre processus, différent du processus principal (celui qui gère l'interface graphique). C'est ce qu'on appelle un fonctionnement asynchrone.


     


    Le principal problème des processus asynchrones c'est de savoir quand ils sont finis, pour récupérer le résultat des traitements. De manière basique, on peut afficher un texte quand c'est terminé.



    func traitementLong() {
    for n in 0...100000000 {
    resultat += n
    }
    print ("Résultat du calcul : ", resultat)
    }

    a



     


    Début du calcul


    Fin des opérations


    Résultat du calcul :  5000000050000000



     


     


    On peut signaler la fin du calcul de plusieurs manières, avec une notification par exemple. La méthode moderne, c'est d'utiliser une closure pour passer un morceau de code a exécuter.



    func traitementLong(completion: ()->()) {
    for n in 0...100000000 {
    resultat += n
    }
    // Exécution de la completion
    completion()
    }

    a


    Utilisation :


    a



    print ("Début du calcul")
    let closureDeFin = {
    print ("(completion) Fin du calcul : ", self.resultat)
    }

    // Création d'un processus en tâche de fond
    DispatchQueue.global().async {
    self.traitementLong(completion: closureDeFin)
    }
    print ("Fin des opérations")

    a



     


    Début du calcul


    Fin des opérations


    (completion) Fin du calcul :  5000000050000000



     


     


    Lorsque le calcul est terminé, l'application exécute la completion qui affiche le texte (completion) Fin du calcul : valeur.


     


    Voilà . Tu as un exemple de code asynchrone et d'utilisation d'une completion pour signaler la fin d'une opération en tâche de fond. J'espère que cela te sera utile (ou a un autre lecteur du forum).

  • DrakenDraken Membre
    décembre 2017 modifié #34


     


    J'ai mis completionHandler() à  la fin de la méthode montageDent, et c'est pire, il manque la moitié des actions de montageDent


     




    Je ne suis pas surpris. TOUTES LES OPERATIONS GRAPHIQUES LIEES A L'INTERFACE DOIVENT SE FAIRE SUR LE PROCESSUS PRINCIPAL. Si tu utilises un thread (processus) secondaire pour accélérer la vitesse de tracé d'une opération sur un contrôle graphique de l'écran (comme tracer un path sur une vue), il peut se passer n'importe quoi : opérations non réalisés, plantages,  etc ..


  • Merci Draken pour ce cours magnifique.


    Je me le met de côté, et je vais travailler là -dessus pour mieux comprendre tout ça.


     


     


    Dans ton premier code, chez moi, "resultat" est toujours = 0 (évidemment pas avec ton code mais avec le mien), car (Mon traitementLong()) semble plus long.


    Ben si tu veux, et là  je vais me faire tuer, mais je ne vois pas comment faire autrement. Le re-dessinage du path se fait en continu, selon un "pan" sur un point.


     


    Je vais travailler la suite de ton post et vais surement trouver comment faire.


     


    Encore merci pour toutes ces explications même si je ne comprend pas tout, tout de suite.



  •  


    Tout est bloqué pendant le calcul. L'interface utilisateur est figée. Les contrôles graphiques ne fonctionnent plus, etc


     


    Pour éviter les blocages du système, il faut exécuter le calcul dans un processus secondaire, de manière asynchrone.



     


     


    C'est justement le contraire, je veux que ça bloque et n'avance que quand c'est fini... :o


  • Joanna CarterJoanna Carter Membre, Modérateur

    N'oublies pas que, si le calcul est initié dans un @IBAction, il faut utiliser :



    @IBAction func clic()
    {
    DispatchQueue.global(qos: .userInitiated).async
    {
    // faire le calcul

    DispatchQueue.main.async
    {
    // mettre à  jour l'UI
    }
    }
    }


    ... pour que le calcul soit fait comme tâche de fond mais la mise à  jour de l'UI soit fait sur la thread principal


  • Normal :


    si un masque se construit à  partir d'un path qui se modifie, le nouveau mask doit tenir compte du nouveau path, et donc se créer qu'après la fin de création du path



  •  


     


    @IBAction func clic()
    {
    DispatchQueue.global ...........

    Hummm c'est bon ça.


     


    Je vais essayer, ça à  l'air de correspondre; sauf que moi c'est pas sur un bouton mais :


    Dans un premier temps au lancement


    Dans un second temps en manipulant une poignée (un gros point rond) qui modifie le path, et donc avec un (UIPanGestureRecognizer)


  • J'ai essayé ça :



    DispatchQueue.global(qos: .userInitiated).async {
    print("1")

    DispatchQueue.main.async {
    print("2")
    }
    }

    pas d'erreur, mais avec ça :



    DispatchQueue.global(qos: .userInitiated).async {
    print("1")

    self.montageDent()

    DispatchQueue.main.async {
    print("2")
    }
    }

    j'ai le warning suivant (traduction google)



     


     


    Cette application modifie le moteur autolayout à  partir d'un thread d'arrière-plan après l'accès au moteur à  partir du thread principal. Cela peut conduire à  la corruption du moteur et des accidents étranges

    dans montageDent je fais entre autre, un :



    pathDent = PathDent11()

    ou (PathDent11) est la classe qui contient le path et ses transformations. Là  doit être le pb.


    Je continue mes essais


  • Tu ne peux modifier l'UI que dans le main thread. Et je suppose donc que self.montageDent() touche à  l'UI, d'où l'erreur.


  • DrakenDraken Membre
    décembre 2017 modifié #42

     


     


    Cette application modifie le moteur autolayout à  partir d'un thread d'arrière-plan après l'accès au moteur à  partir du thread principal. Cela peut conduire à  la corruption du moteur et des accidents étranges

     


    Bah oui, c'est normal. Comme je te l'ai dis plus haut, les opérations liées à  l'interface doivent se dérouler dans le thread principal, sous peine de comportements étranges et imprévisibles.


     


    Tu crées un nouveau thread avec DispatchQueue.global avant de lui demander d'exécuter ton rendu. CEST MAL, et Xcode te le signale par un warning.


     


    Si tu veux agir sur l'interface graphique dans un thread, il faut le faire avec DispatchQueue.main qui retourne le thread courant au lieu d'en créer un autre. C'est justement ce que fait nounours.e dans son exemple. Elle génère un nouveau thread pour faire un calcul en tâche de fond, mais utilise le thread principal (grâce à  DispatchQueue.main) pour communiquer avec l'interface graphique.


     


    L'utilisation des thread n'est pas la solution à  ton problème. Tu veux modifier en temps réel la forme d'un objet graphique complexe, avec un MOTEUR GRAPHIQUE TROP LENT ! Tu n'y arriveras pas, à  moins peut-être d'utiliser un iPad Pro 2025 à  32 cores sous iOS 22. Tu dois changer les paramètres de l'équation. Soit :


    - Changer de moteur graphique (utiliser une SKView et dessiner dedans une forme en 2D avec le framework SpriteKit)


    - Ne pas dessiner l'image en temps réel, juste les contours avec PLUSIEURS PATHS et créer le masque quand l'utilisateur cesse de déplacer les points de contrôles de ton masque


     


    Je t'ai déjà  expliqué ça dans un autre topic. 


  • busterTheobusterTheo Membre
    décembre 2017 modifié #43

    Encore merci pour tous vos efforts, j'allucine, quelle disponibilité. J'aimerai en donner autant.


     


    Je met "Résolu".


     


    Suite à  vos remarques, et après de multiples essais, j'ai déplacé la méthode qui fait le masque, dans la classe qui contient le path et ses transformations.


    Puis je l'appelle (au sein de cette classe) après la fin "du dessin du path" et "de la préparation des points d'action sur ce path" qui seront utiles pour les futures manips de déformation de ce path :



    bezierNewPathVersNewAncre()
    maillage()

    creerMasque()

    Au passage, j'ai pu m'immerger un peu plus profondément dans le GCD, et c'est vrai, pour l'instant je n'en ai pas besoin.


    D'ailleurs, à  ce sujet, j'ai pu supprimer tous les Dispatch.


     


    Ce que je trouve intéressant, c'est que cela me pousse encore d'avantage à  bien réfléchir à  "où, une action (appel de méthode, ...) doit elle être activée", et aussi quand, bien sûr.


     


    J'étais jusqu'à  présent sensible au "quand" et là , le "où" me sollicite.


    Par "où", j'entend : à  quel niveau de la hiérarchie des views.


     


    Donc,  :p   :D   :p  


Connectez-vous ou Inscrivez-vous pour répondre.