IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Tkinter Python Discussion :

Problème pour terminer un thread


Sujet :

Tkinter Python

  1. #1
    Membre éclairé Avatar de crocodilex
    Profil pro
    Inscrit en
    Mars 2006
    Messages
    697
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2006
    Messages : 697
    Points : 858
    Points
    858
    Par défaut Problème pour terminer un thread
    Bonjour à tous,
    Je débute en python.
    J'ai un soucis pour arrêter un thread lorsque je clique sur la croix pour fermer mon appli.
    Je reste bloqué dans le join().
    A priori cela viendrait dans l'appel de la méthode getState() de ma class LabeledCheckbutton.
    Hélas je ne comprends pas bien ce qui se passe.
    D'avance, merci pour votre aide.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
     
    #coding:utf-8
    import tkinter as tk
    import time
    from threading import Thread
     
    class LabeledCheckbutton(tk.Frame):
        def __init__(self, parent):
            tk.Frame.__init__(self, parent)
            self.checkVar = tk.IntVar()
            self.checkbutton = tk.Checkbutton(self, text="Click on me.....", variable=self.checkVar)
            self.checkbutton.grid(row=1, column=0)
     
        def getState(self):
            return( self.checkVar.get() )
     
    class MyFrame(tk.LabelFrame, Thread):
        def __init__(self, parent):
            tk.LabelFrame.__init__(self, parent)
            Thread.__init__(self)
            self.grid(row=0, column=0)
            self.terminateThread = False
            self.lblchk = LabeledCheckbutton(self)
            self.lblchk.grid(row=0, column=0)
            self.temp = 0
     
        def run(self):
            while self.terminateThread == False:
                time.sleep(2)
                self.temp = self.lblchk.getState()
                print("Checkbox value = " + str(self.temp))
            print("Exit Thread")
     
     
        def killTask(self):
            self.terminateThread = True
            print("killing Task !!!!!!!") 
     
     
    def on_closing():
        myFrame.killTask()
        myFrame.join()
        print("Terminated !!!!!!!")
        root.destroy()
     
    if __name__ == "__main__":
        root = tk.Tk()
        root.protocol("WM_DELETE_WINDOW", on_closing)
        myFrame = MyFrame(root)
        myFrame.start()
        root.mainloop()

  2. #2
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 352
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 352
    Points : 36 879
    Points
    36 879
    Par défaut
    Salut,

    Un GUI comme tkinter réalise un multitâche coopératif alors que les threads font un multitâche pré-emptif.

    Dans un multitâche coopératif, la synchronisation de l'accès à l'état du programme est réalisée par la garantie d'avoir un seul callback actif à l'instant t. Entre le début et la fin de la fonction qui réalise le callback, une condition vraie à l'entrée de la fonction restera vraie à sa sortie (sauf modification par le code de la fonction).

    Avec des threads, le callback peut être préempté et une condition vraie au début peut être fausse à la sortie (parce qu'un autre thread se sera activé entre temps).

    Donc on garde le GUI dans le thread principal et s'il est nécessaire d'avoir des threads, on s'interdit de modifier l'état des widgets dans son contexte pour "poster" une opération à exécuter dans le contexte du thread du GUI.

    Et si on veut ordonnancer une opération périodique, on utilisera .after qui permet de la déclencher dans le thread du GUI directement.

    Dit autrement, comme vous n'avez pas de bonne raison d'utiliser des threads dans votre code, le problème que vous avez n'existe pas : vous l'avez fabriqué par ignorance du bon usage de tkinter et des threads.

    - W

  3. #3
    Membre éclairé Avatar de crocodilex
    Profil pro
    Inscrit en
    Mars 2006
    Messages
    697
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2006
    Messages : 697
    Points : 858
    Points
    858
    Par défaut
    Merci pour cette réponse votre éminent Expert.
    Comme je le disais dans le post initial, je débute en Python et votre réponse ne m'est pas d'une grande utilité.
    Afin de ne pas polluer le forum avec mes 10000 lignes de code, j'ai envoyé ici un code épuré avec le strict minimum.
    Il ne faut pas être un génie pour voir que ce thread, dans ce cas précis, ne sert à rien.
    Et si j'ai crée un thread, c'est que j'en ai besoin pour des bonnes raisons.
    Certes il n'est peut être pas crée au bon endroit ou pas fait dans les règles de l'art, mais encore une fois, je le répète, je débute.
    Le but de ce forum est d'aider et non pas de clasher.
    Alors, s'il vous plait, vous êtes à la retraite, ne gâchez pas votre énergie.

  4. #4
    Membre régulier
    Inscrit en
    Juillet 2013
    Messages
    80
    Détails du profil
    Informations forums :
    Inscription : Juillet 2013
    Messages : 80
    Points : 119
    Points
    119
    Par défaut
    Hello,

    Je ne comprends pas pourquoi vous tenez à insérer un thread dans ce code ; de ce que je comprends, vous déclenchez l'affichage d'un message à chaque fois que vous cliquez sur le bouton, ce qui est une utilisation "classique" de Tkinter ?

    Je ne suis pas expert en threading mais si je comprends bien le process :
    1) Vous appelez la commande de fermeture de la fenêtre
    2) Avant de fermer la fenêtre, le code appelle la fonction killTask() pour changer la valeur du membre terminateThread
    3) On attend que le thread termine (avec le join())

    Problème : votre thread n'en est pas vraiment un au sens où il n'execute pas quelque chose. Vous avez essentiellement créé un objet Frame qui est là en attendant une série d'instructions (idéalement de root) mais il n'y a pas vraiment de moment où il peut se dire "ok, j'ai terminé". Donc votre join() attendra indéfiniment...

    Comme je l'ai dit plus haut, à votre place, j'enlèverai le threading ici, car il n'a aucun intérêt. Mais si vous tenez à le laisser, vous pouvez utiliser la méthode setDaemon() de la composante thread de MyFrame ; en la mettant à "True", le thread sera "tué" dès que le thread principal (mainloop) aura fini son execution, même si le thread démoniaque est en cours d'execution.
    Bien évidemment, il faut quand même enlever le join(), sinon vous restez bloqué dans la boucle infinie puisque root ne pas terminer sa tâche de fermeture qui va déclencher la mort du démon...

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
     
    import tkinter as tk
    import time
    from threading import Thread
     
    class LabeledCheckbutton(tk.Frame):
        def __init__(self, parent):
            tk.Frame.__init__(self, parent)
            self.checkVar = tk.IntVar()
            self.checkbutton = tk.Checkbutton(self, text="Click on me.....", variable=self.checkVar)
            self.checkbutton.grid(row=1, column=0)
     
        def getState(self):
            return( self.checkVar.get() )
     
    class MyFrame(tk.LabelFrame, Thread):
        def __init__(self, parent):
            tk.LabelFrame.__init__(self, parent)
            Thread.__init__(self)
            self.grid(row=0, column=0)
            self.terminateThread = False
            self.lblchk = LabeledCheckbutton(self)
            self.lblchk.grid(row=0, column=0)
            self.temp = 0
            self.setDaemon(True)
     
        def run(self):
            while self.terminateThread == False:
                time.sleep(2)
                self.temp = self.lblchk.getState()
                print("Checkbox value = " + str(self.temp))
            print("Exit Thread")
     
     
        def killTask(self):
            self.terminateThread = True
            print("killing Task !!!!!!!")
     
     
    def on_closing():
        myFrame.killTask()
    #    myFrame.join()
        print("Terminated !!!!!!!")
        root.destroy()
     
    if __name__ == "__main__":
        root = tk.Tk()
        root.protocol("WM_DELETE_WINDOW", on_closing)
        myFrame = MyFrame(root)
        myFrame.start()
        root.mainloop()

  5. #5
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 352
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 352
    Points : 36 879
    Points
    36 879
    Par défaut
    Salut,

    Citation Envoyé par crocodilex Voir le message
    Il ne faut pas être un génie pour voir que ce thread, dans ce cas précis, ne sert à rien.
    Et si j'ai crée un thread, c'est que j'en ai besoin pour des bonnes raisons.
    Virez tkinter et côté thread, çà devrait marcher.
    Le problème est dans le mélange entre tkinter et les threads: çà ne se fait pas comme çà... et vous avez plein d'exemples sur Internet.
    Et comme çà ne se fait pas comme çà, personne n'ira se prendre le chou à faire fonctionner ce code là.

    Citation Envoyé par charliemtx Voir le message
    Bien évidemment, il faut quand même enlever le join(), sinon vous restez bloqué dans la boucle infinie puisque root ne pas terminer sa tâche de fermeture qui va déclencher la mort du démon...
    Il n'y a pas de boucle: juste que la machinerie tk détient un verrou qui bloque l'exécution des autres threads. Ce qui est licite car un callback ne doit pas contenir d'appel bloquant: .join attend le temps qu'il faut... mais, le thread ne se termine jamais.

    La seule chose qu'on peut faire, c'est supprimer ce callback: la destruction de l'application graphique provoque la sortie de la mainloop, on positionne le flag de fin et on attend la fin du thread après cette instruction.

    Pour le reste, c'est un exemple d'un truc qui ne peut pas marcher (mais qu'on peut faire fonctionner raisonnablement autrement) parce que threads et GUI ne se mélangent pas comme çà...

    - W

  6. #6
    Membre éclairé Avatar de crocodilex
    Profil pro
    Inscrit en
    Mars 2006
    Messages
    697
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2006
    Messages : 697
    Points : 858
    Points
    858
    Par défaut
    Bonjour,
    Citation Envoyé par wiztricks Voir le message
    [...]
    Le problème est dans le mélange entre tkinter et les threads: çà ne se fait pas comme çà... et vous avez plein d'exemples sur Internet.
    [...]
    - W
    Cette réponse m'aurait suffit.

    Sinon, pour résumer, voici ce que je souhaitais faire :
    - Envoyer et recevoir de manière périodique des données sur une liason série.
    - Ces données sont issues de l'interaction de l'utilisateur sur des widgets contenus dans un container de type LabelFrame.
    Et comme je voulais plusieurs instances de ce container, l'idée était de créer une classe héritée de LabelFrame..
    Concernant l'envoie périodique des données, je pensais qu'un thread par container ferait l'affaire. D'où l'idée de faire hériter ma classe de la classe Thread.
    Donc si je comprends bien vos remarques, faire dériver ma classe d'une classe LabelFrame et d'une classe Thread n'est pas une bonne idée. C'est noté.
    Je ne connaissais pas non plus la méthode after() des objets tkinter. A priori cela pourrait faire l'affaire.
    Merci à vous deux.

  7. #7
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 352
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 352
    Points : 36 879
    Points
    36 879
    Par défaut
    Citation Envoyé par crocodilex Voir le message
    Concernant l'envoie périodique des données, je pensais qu'un thread par container ferait l'affaire. D'où l'idée de faire hériter ma classe de la classe Thread.
    MyFrame(tk.LabelFrame, Thread) est une très mauvaise idée car on se retrouvera avec méthodes et attributs accédés via l'un ou l'autre thread. Si on fait de la POO, c'est justement pour éviter le mélange des genres!

    Citation Envoyé par crocodilex Voir le message
    Je ne connaissais pas non plus la méthode after() des objets tkinter. A priori cela pourrait faire l'affaire.
    Et pour l'envoi périodique de données entre tkinter et des threads, passer par des queue.Queue sera "safe" (quelle que soit la mécanique qui déclenche le callback qui...) et s'applique entre n'importe quels threads.

    - W

  8. #8
    Membre éclairé Avatar de crocodilex
    Profil pro
    Inscrit en
    Mars 2006
    Messages
    697
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2006
    Messages : 697
    Points : 858
    Points
    858
    Par défaut
    Citation Envoyé par wiztricks Voir le message
    MyFrame(tk.LabelFrame, Thread) est une très mauvaise idée car on se retrouvera avec méthodes et attributs accédés via l'un ou l'autre thread. Si on fait de la POO, c'est justement pour éviter le mélange des genres!
    J'ai malheureusement trouvé cette façon de faire sur le net.

    Citation Envoyé par wiztricks Voir le message
    Et pour l'envoi périodique de données entre tkinter et des threads, passer par des queue.Queue sera "safe" (quelle que soit la mécanique qui déclenche le callback qui...) et s'applique entre n'importe quels threads.
    Ok, je prends.

Discussions similaires

  1. Problème pour générer un thread
    Par siro75 dans le forum Bibliothèques, systèmes et outils
    Réponses: 3
    Dernier message: 29/01/2014, 15h16
  2. [Pascal / SDL] Problème pour gérer les Threads…
    Par Sn00ze92 dans le forum Langage
    Réponses: 2
    Dernier message: 24/02/2009, 17h40
  3. Problème pour tuer un Thread
    Par demcoul dans le forum Concurrence et multi-thread
    Réponses: 5
    Dernier message: 29/12/2008, 19h31
  4. Problème pour terminer un thread
    Par BestFF dans le forum Threads & Processus
    Réponses: 6
    Dernier message: 05/07/2008, 22h03
  5. Probléme pour tuer un Thread
    Par peyo_le_fou dans le forum POSIX
    Réponses: 5
    Dernier message: 04/11/2006, 14h10

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo