2
votes

Comment rendre le thread Tkinter GUI sûr?

J'ai écrit un morceau de code où j'ai une interface graphique simple avec un canevas. Sur cette toile, je dessine un Matplot. Le Matplot est mis à jour toutes les secondes avec les données d'une base de données SQ Lite que je remplis avec de fausses informations de capteur (juste pour tester pour le moment). seconde. J'ai même essayé de mettre à jour l'intrigue dans un autre fil. Mais même là, j'obtiens un décalage.

Avec mon nouveau code, j'ai fait fonctionner la plupart de mes choses. Le filetage aide à empêcher mon interface graphique / fenêtre de se figer pendant la mise à jour du canevas.

La dernière chose qui me manque est de sécuriser les threads.

Voici le message que je reçois:

CREATE TABLE `Sensor_Stream_1` (
    `Date`  TEXT,
    `Temperature`   INTEGER,
    `Humidity`  INTEGER
);


21 commentaires

Je créerais une classe distincte qui gère le dessin des données, puis transmettrais le canevas à cette classe tout en l'exécutant dans son propre thread. Cela devrait éviter le décalage que vous voyez. Cela dit, j'ai déjà utilisé matplotlib et je n'ai pas eu de problème de décalage, il se peut donc que quelque chose que vous ayez dans votre code soit à l'origine du retard.


Avez-vous un exemple pour cela? Je n'ai jamais travaillé avec des threads auparavant.


Il existe de nombreux exemples de threading ici sur le débordement de pile. Je regarde votre code maintenant pour voir si quelque chose se détache qui peut causer ce décalage.


D'accord, merci d'avoir jeté un œil sur mon code


@ Mike-SMT j'ai édité mon code. J'ai essayé d'utiliser le multithreading. Ne semble pas bien fonctionner.


Je me demande si votre retard provient de la requête de base de données. Exécutez peut-être votre requête de base de données dans un thread. Je ne pense pas que le retard soit dû au dessin.


Pourriez-vous jeter un oeil sur le Code qui est affiché au bas de mon message? J'ai essayé d'utiliser le filetage mais je ne sais pas vraiment où placer les éléments dans le code.


Il est difficile de tester sans avoir une base de données à partir de laquelle extraire. Vous pouvez peut-être ajouter un dictionnaire simple avec un exemple de données à utiliser dans le code en tant que base de données fictive.


J'ai édité le code avec les données codées en dur. Il suffit de dessiner une ligne maintenant mais d'obtenir toujours les décalages. J'espère que vous pourrez le tester comme ça!


@ Mike-SMT a ajouté mon nouveau code au bas de l'article. Juste la bonne Intégration de l'animation à gauche.


@ Mike-SMTI a ajouté une prime, juste au cas où vous voudriez revoir :)


D'accord, vous pouvez le trouver ici: gist.github.com/KDoeTS/7534e4bf4370f5f0cfd73 ajouté a389e juste le premier code, je gues mon essai multithreading est la corbeille?


@stovfl J'ai à nouveau mis à jour le Github Gist pour afficher mes derniers progrès. J'ai mis le filetage au travail mais maintenant j'ai le problème que Tkinter n'est pas vraiment thread-safe. J'ai reçu ce message après un certain temps: RuntimeError: le thread principal n'est pas dans la boucle principale


@BryanOakley j'ai supprimé SQLite du code et ajouté une liste de 20 nombres aléatoires. Cela rend l'exemple beaucoup plus court. Je ne suis pas sûr mais je pense que le reste est nécessaire pour causer / montrer mon problème. Merci pour l'indice!


@ K-Doe: au lieu de dire "je ne suis pas sûr mais ...", pourquoi ne pas l'essayer et voir ce qui se passe pour être sûr? C'est ainsi que vous déboguez les problèmes - continuez à réduire le code jusqu'à ce que vous ayez le moins de lignes de code qui vous posent encore le problème.


@BryanOakley je suis dessus! Mais cela prend du temps, vous le savez peut-être car vous semblez être beaucoup plus expérimenté que moi. Si je n'ai pas de nouvelles valeurs ou si je ne dessine rien de nouveau sur ma toile, il n'y a pas d'erreur pour autant que j'ai testé. Cela semble donc être le code minimal que je peux publier.


@BryanOakley est-il même possible de mettre à jour l'interface graphique de Tkinter de n'importe où ailleurs qu'à partir du fil principal? J'ai essayé le code ci-dessus, également avec Queue, mais avec Queue, je dois à nouveau dessiner sur le canevas à partir du fil principal, ce qui fait geler ma fenêtre (court avec un tracé beaucoup plus avec plus de tracés à dessiner). Avec mes connaissances de débutant, je suis lentement à la fin - je ne sais plus quoi tester.


@ K-Doe: cela dépend de la façon dont vous définissez «possible». Oui, il est possible d'exécuter tkinter dans un thread et d'exécuter un autre code dans un autre thread. Et oui, il est possible pour l'interface graphique d'afficher les changements causés par l'autre thread. En règle générale, vous ne devriez jamais essayer d'accéder aux objets tkinter directement à partir de plus d'un thread, mais d'autres threads peuvent utiliser une file d'attente pour envoyer des informations au thread tkinter, et le thread tkinter peut lire les informations de cette file d'attente.


@BryanOakley mais que le canevas est mis à jour à partir de la file d'attente qui se trouve dans le thread tkinter et provoque à nouveau le gel, ou ai-je tort?


Je ne sais rien du fonctionnement de matplotlib. Il ne fait aucun doute qu'essayer d'utiliser tkinter avec des threads est très difficile.


Connaissez-vous un autre moyen (le mieux serait un débutant) d'afficher des données en direct dans une interface graphique?


3 Réponses :


4
votes

Votre processus GUI ne doit s'exécuter dans aucun thread. seule l'acquisition de données doit être threadée.

Lorsque cela est nécessaire, les données acquises sont transférées vers le processus gui (ou le processus gui notifié à partir des nouvelles données disponibles). Je devrai peut-être utiliser un mutex pour partager des ressources de données entre le thread d'acquisition et l'interface graphique (lors de la copie)

la boucle principale ressemblera à:

running = True
while running:
    root.update()
    if data_available:
        copydata_to_gui()
root.quit()


0 commentaires

0
votes

Cette fonction est appelée toutes les secondes, et elle sort du rafraîchissement normal.

def start(self,parent):
    self.close=False
    self.Refresh(parent)

def Refresh(self,parent):
    '''your code'''
    if(self.close == False):
        frame.after( UpdateDelay*1000, self.Refresh, parent)

La fonction est appelée seule, et tout ce qui se passe à l'intérieur ne bloque pas le fonctionnement normal de l'interface .


0 commentaires

2
votes

J'ai eu le même problème avec tkinter et utiliser les événements pypubsub était ma solution. Comme le suggèrent les commentaires ci-dessus, vous devez exécuter votre calcul dans un autre thread, puis l'envoyer au thread d'interface graphique.

import time
import tkinter as tk
import threading
from pubsub import pub

lock = threading.Lock()


class MainApplication(tk.Frame):
    def __init__(self, parent, *args, **kwargs):
        tk.Frame.__init__(self, parent, *args, **kwargs)
        self.parent = parent
        self.label = tk.Label(root, text="Temperature / Humidity")
        self.label.pack(side="top", fill="both", expand=True)

    def listener(self, plot_data):
        with lock:
            """do your plot drawing things here"""
            self.label.configure(text=plot_data)


class WorkerThread(threading.Thread):
    def __init__(self):
        super(WorkerThread, self).__init__()
        self.daemon = True  # do not keep thread after app exit
        self._stop = False

    def run(self):
        """calculate your plot data here"""    
        for i in range(100):
            if self._stop:
                break
            time.sleep(1)
            pub.sendMessage('listener', text=str(i))


if __name__ == "__main__":
    root = tk.Tk()
    root.wm_geometry("320x240+100+100")

    main = MainApplication(root)
    main.pack(side="top", fill="both", expand=True)

    pub.subscribe(main.listener, 'listener')

    wt = WorkerThread()
    wt.start()

    root.mainloop()


0 commentaires