[Python3] Gestion multithread - progressbar
#1

J'ai un petit projet auquel je tiens, qui s'appelle YUP.py
C'est actuellement du python3 + tkinter. (actuellement, opérationnel que sur OpenBSD)

J'aurais besoin d'un coup de main pour savoir comment gérer une "progressbar", peut-être en multithread afin que la fenêtre principale affiche une progression voire des messages le temps du traitement du processus principal.
Et, là, j'avoue que le bât blesse. Je ne comprends pas comment m'y prendre ?!

GPG:Fingerprint ed25519 : 072A 4DA2 8AFD 868D 74CF  9EA2 B85E 9ADA C377 5E8E
GPG:Fingerprint rsa4096 : 4E0D 4AF7 77F5 0FAE A35D  5B62 D0FF 7361 59BF 1733
Répondre
#2

Il y a 2 jours, j'ai fait une petit appli Python3 tkinter qui pose des questions avec un timer pour chaque question. Le timer se sert du module threading.

Dans ton cas, je pense que c'est différent. Il faudrait que le processus qui fait le boulot ait un moyen de communiquer avec lton GUI (la progressbar).

Voila un truc qui marche pour moi. Je ne sais pas si c'est propre!!
Tu commences par reprendre la classe threading.Thread pour pouvoir l'étendre et ajouter un argument à son constructeur. Cet élément c'est le GUI (ou juste la progressbar, comme tu veux)
Dans la fonction du worker qui fait le boulot (do_stuff), j'appelle quand j'ai envie une fonction du gui qui va mettre à jour la progressbar.
Dans le gui, tu fait en sorte qu'un bouton démarre ton worker et bien sur, tu ajoutes la fonction que le worker va pouvoir appeler pour te dire où il en est.

Code :
#!/usr/bin/env python3


import time
import threading

class Worker(threading.Thread):
    def __init__(self, gui):
        super().__init__(target=self.do_stuff)
        self.gui = gui

    def do_stuff(self):
        for i in range(100):
            time.sleep(1)#fait des trucs là
            self.gui.updateProgress(i)

class Gui(tkinter.Frame):
    def __init__(self,master):
       super().__init__(self,master)
        self.worker = Worker(self)
        #tu construis ton gui
       self.button = tkinter.button(...,command=onButton)
       self.progressbar = ???
      
    def onButton(self):
        self.worker.start()

    def updateProgress(self,i):
        print(i)
       #self.progressBar.set(i)

J'espère que ça répond en partie au moins à ta question.
Si ta question porte sur comment construire un widget progressbar, je pense que c'est faisable avec une iamge que tu déplaces en fonction de i.

P.S: Mon indentation est pourrie. Le forum m'a bouffé des espaces/tabs pendant le copier/coller
Répondre
#3

Bon, je vais essayer. Je ne sais pas si j'ai tout compris... mais merci quand même !

Ce n'est pas la pgbar qui me pose soucis.

Ce que j'aimerais arriver à faire, c'est le temps que mon script envoie l'image chez l'hébergeur d'image et retourne une réponse, la pgbar s'anime...
Pour une animation de type progressive, pas de soucis... on verra après si je peux envoyer des messages dedans.

Là, dans l'immédiat, sans la gestion de thread, mon code que pour l'instant j'ai commenté, s'exécutait après le retour de réponse du service hébergeur !

Bref, test !
Si je comprends bien, je n'appelle plus directement ma class Gui, mais d'abord la class Worker ! C'est bien ça ?!

GPG:Fingerprint ed25519 : 072A 4DA2 8AFD 868D 74CF  9EA2 B85E 9ADA C377 5E8E
GPG:Fingerprint rsa4096 : 4E0D 4AF7 77F5 0FAE A35D  5B62 D0FF 7361 59BF 1733
Répondre
#4

Je t'ai posté un truc en en décrivant un autre...

Tu veux que la barre s'anime comme un gif ou qu'elle représente le progrès actuel de l'upload ?

(16-04-2018, 22:43:23)PengouinBSD a écrit :  Si je comprends bien, je n'appelle plus directement ma class Gui, mais d'abord la class Worker ! C'est bien ça ?!
Non Smile
Je t'ai refait un code qui marche.
Tout en bas, tu as le "main" : Le gui s'initialise, lance le worker (g.upload()) puis affiche un "hello" à 2.8 secondes d'intervalle indépendamment de ce que fais le worker.
Le worker pendant ce temps, te tient au courant en envoyant régulièrement son progrès au gui. Pour faire ça, il appelle une fonction du gui et passe son progrès en argument (gui.progressUpdate(i))

On peut imaginer que le gui fait évoluer la pgbar en fonction de ce que lui renvoie le worker à traver updateProgress. En même temps, l'utilisateur continue d'utiliser le gui pour préparer un 2ème upload par exemple.

Les 2 trucs qui peuvent t'intéresser en particulier dans mon script c'est :
-le Worker qui hérite du Thread et qui ajoute le gui à son scope (self.gui = gui). Ça permet d'acceder à gui.updateProcess depuis le worker par la suite.
- si tu rallonges la boucle du worker soit prudent. Tu ne peux plus la tuer. ctrl+C tue le gui mais le processus worker continue de se servir de gui.updateProgress() pour afficher son progrès un peu comme un nécromancien continue de se servir des morts. Elle est à 5 pour l'instant ça va. Je l'avais mis à 100 au début comme un crétin. Par la suite, pense à mettre un bouton qui appelle self.worker.cancel() pour pouvoir tuer le processus depuis le gui.

Voila le script chmod +x test.py && ./test.py
Code :
#!/usr/bin/env python3

import time
import threading

class Worker(threading.Thread):
    def __init__(self, gui):
        super().__init__(target=self.do_stuff)
        self.gui = gui

    def do_stuff(self):
        for i in range(5):
            time.sleep(1)
            self.gui.updateProgress(i)

class Gui():
    def __init__(self):
        self.worker = Worker(self)

    def upload(self):
        self.worker.start()

    def hello(self):
        print("hello")

    def updateProgress(self,i):
        print("gui uploadProgress",i)

if __name__ == "__main__":
    g = Gui()
    g.upload()
    g.hello()
    time.sleep(2.8)
    g.hello()

P.S: J'ai pas eu le temps de regarder ton code. Je vais me coucher.
Répondre
#5

Bon, je reviendrais sur ça après mon dodo. Là, il est tard.

J'ai déjà un bon début, avec curseur tournant le temps d'envoi de l'image et de réception des informations...
C'est la partie relative à la pgbar qui s'effectue malgré tout, après... j'ai dû loupé quelque chose :p

GPG:Fingerprint ed25519 : 072A 4DA2 8AFD 868D 74CF  9EA2 B85E 9ADA C377 5E8E
GPG:Fingerprint rsa4096 : 4E0D 4AF7 77F5 0FAE A35D  5B62 D0FF 7361 59BF 1733
Répondre
#6

Bon, je n'arrive pas à faire en sorte que la pgbar progresse en même temps que le service d'envoi d'image et de réception des données.
Cela s'exécute seulement après !

GPG:Fingerprint ed25519 : 072A 4DA2 8AFD 868D 74CF  9EA2 B85E 9ADA C377 5E8E
GPG:Fingerprint rsa4096 : 4E0D 4AF7 77F5 0FAE A35D  5B62 D0FF 7361 59BF 1733
Répondre
#7

Est ce que mon script a fonctionné et est ce c'est le comportement que tu veux ?

J'ai regardé vite fait ton programme. Comme je ne suis pas entrainé à lire du code, je ne suis pas sûr d'avoir lu ce qu'il fallait.

Partant de YUP.tk.py puis je suis allé voir dans gui.py mais j'ai pas pu tout lire. Donc je ne sais pas où est comment tu initialises et appelles ton worker.
Donc j'ai regardé worker.py, J'ai vu 2 fonctions qui semblaient liées au gui. J'imagine que Worker.read_bits(self) est celle en question ? Tu fais de la recursivité jusqu'à atteindre size en communiquant avec ton Gui via Gui.upd_pgbar, ok.

2 suggestions pour essayer de voir ce qui se passe.
- Met des print(nb) dans Worker.read_bits et dans Gui.upd_pgbar. Ça te permet de voir ce que ton worker fait en direct et ce que ton gui reçoit.
- Dans mon dernier programme python3/tkinter, il a fallu que j'utilise root.update_idletasks() qui force les widgets à se mettre à jour. Chez, root est la variable initialisée avec tkinter.Tk() est dans laquelle je construis mon GUI. Dans ton gui, je crois que tu y accèdes via self.update_idletasks()
Répondre
#8

Ton script fonctionne en effet :

Code :
$ python3 t3.py                                                                                    
hello
gui uploadProgress 0
gui uploadProgress 1
hello
gui uploadProgress 2
gui uploadProgress 3
gui uploadProgress 4

GPG:Fingerprint ed25519 : 072A 4DA2 8AFD 868D 74CF  9EA2 B85E 9ADA C377 5E8E
GPG:Fingerprint rsa4096 : 4E0D 4AF7 77F5 0FAE A35D  5B62 D0FF 7361 59BF 1733
Répondre
#9

Faire du multithread juste pour gérer l'interface du GUI avec tkinter, c'est pas facile et pas toujours efficace.
Heureusement, tout est prévu dans tkinter :

[code]
root.after(100, update)
[code]
Ci-dessus, j'appelle la méthode "after" dans la fenêtre principale qui lancera après 100 ms la fonction update (ça fonctionne très bien aussi en POO).

On peut faire une boucle en rappelant "root.after(100, update)" à la fin de la fonction update Smile
Répondre
#10

Bon, j'abandonne l'idée de gérer une progressbar... je garde les threads, et j'utilise l'astuce du curseur qui change pour indiquer de patienter !

GPG:Fingerprint ed25519 : 072A 4DA2 8AFD 868D 74CF  9EA2 B85E 9ADA C377 5E8E
GPG:Fingerprint rsa4096 : 4E0D 4AF7 77F5 0FAE A35D  5B62 D0FF 7361 59BF 1733
Répondre


Atteindre :


Utilisateur(s) parcourant ce sujet : 1 visiteur(s)