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

PyQt Python Discussion :

Problème de lancement de Thread


Sujet :

PyQt Python

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Nouveau membre du Club
    Homme Profil pro
    Assistant aux utilisateurs
    Inscrit en
    Avril 2020
    Messages
    7
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Seine et Marne (Île de France)

    Informations professionnelles :
    Activité : Assistant aux utilisateurs

    Informations forums :
    Inscription : Avril 2020
    Messages : 7
    Par défaut Problème de lancement de Thread
    Bonjour à tous,

    Je code actuellement un logiciel de ticketing pour suivre mon activité.

    Ce logiciel charge différents UI en fonction du profil de l'utilisateur ( utilisateur, admin, direction).
    Certains de ces profils comportent des appels à des fonction très longues ( interaction avec Outlook pour compter les mails par win32com).

    Je cherche à faire en sorte d'afficher un écran de chargement afin que les utilisateurs sachent bien que le logiciel n'a pas planté, mais charge juste le profil.

    Pour ce faire, je me suis inspiré d'un script de Tyrtamos ou il montrait comment justement lancer un throbber durant une tache longue grâce au thread.

    Mon soucis, c'est que visiblement mes 2 taches ( chargement de l'UI, et affichage du throbber) ne s'effectuent pas en simultané.

    Le throbber s'affiche, se fige durant le chargement de l'UI, et recommence à tourner quand l'UI est chargée.


    Voici le code

    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
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
     
    import sys, os
    import time
     
    from PyQt5.QtCore import (Qt, QSize, pyqtSlot, pyqtSignal, QThread)
    from PyQt5.QtGui import (QMovie)
    from PyQt5.QtWidgets import (QApplication, QWidget, QPushButton, QLabel, 
                                 QGridLayout)
    import Ui_Admin_livrable
    import Ui_Direction_livrable
    import Ui_User_livrable
    from PyQt5 import QtCore
    #############################################################################
     
    class ThrobberWindow(QLabel):
        """petite fenêtre qui affiche un throbber (image gif animée) pour 
           signaler une activité en cours et faire patienter l'utilisateur
        """
     
        #========================================================================
        def __init__(self, parent=None):
            super(ThrobberWindow, self).__init__(parent)
     
            # image gif animée du throbber
            # configure la fenêtre
            self.setWindowTitle(" ") # supprime le titre de la fenêtre
            self.resize(400, 200) # met une fenêtre carrée
            self.setFixedSize(self.width(), self.height()) # pas de redimensionnement
            # le contenu du QLabel sera au milieu (hor. et vert.)
            self.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
     
            # met seulement un cadre autour de la fenêtre du throbber
            # NB: mais on peut encore la déplacer à la souris!
            newflags = Qt.Dialog
            newflags |= Qt.CustomizeWindowHint  # permet la personnalisation
            newflags &= ~Qt.WindowCloseButtonHint  # pas de case de fermeture
            newflags &= ~Qt.WindowSystemMenuHint  # pas de menu de fenêtre
            newflags &= ~Qt.WindowContextHelpButtonHint # pas de case "?"
            self.setWindowFlags(newflags)
     
            # crée un movie avec le throbber sous forme d'image "gif" animée
            self.movie = QMovie()# j'ai été obligé d'hotter le gif pour des raisons de confidentialité
            self.movie.setScaledSize(QSize(400, 200)) # fixe dimension 
     
            self.setMovie(self.movie) # affecte le movie au QLabel
            self.movie.start() # lance l'animation du throbber
     
     
    #############################################################################
    class Fenetre(QWidget):
        """fenêtre pour tester le throbber
        """
     
        #========================================================================
        def __init__(self, parent=None):
            super(Fenetre, self).__init__(parent)
     
            self.resize(400, 300)
     
            # créer le bouton
            self.bouton = QPushButton("Lancer le programme!", self)
            self.bouton.clicked.connect(self.lancementprogramme)
     
            # positionne le widget dans la fenêtre
            posit = QGridLayout()
            posit.addWidget(self.bouton, 0, 0)
            self.setLayout(posit)
     
            # pour signaler qu'il n'y a aucun thread en cours
            self.thread = None 
     
     
        #========================================================================
        @pyqtSlot()
        def lancementprogramme(self):
            """slot exécuté lors du clic sur le bouton: lance le thread
            """
            if self.thread != None:
                return # il y a déjà un thread en cours: on ne fait rien
     
            self.throbber_worker=QtCore.QThread()
            self.throbber_worker.start()
     
            # crée le throbber
            self.throbber = ThrobberWindow()
            # option: met la fenêtre du throbber en mode "modal"
            self.throbber.setWindowModality(Qt.ApplicationModal)
            # affiche la petite fenêtre du throbber
            self.throbber.show()
     
            # lance le thread
            self.worker=QtCore.QThread()
            self.worker.start()
     
            self.thread = threading()
            self.thread.moveToThread(self.worker)
            self.thread.close_open_UI_admin()
     
        #========================================================================
     
    class threading (QtCore.QObject):
     
            def __init__(self, parent=None):
                super(threading, self).__init__(parent)
     
            def close_open_UI_admin(self):
                """ferme la fenetre et lance l'environnement choisi"""
                self.UI_admin=Ui_Admin_livrable.App_Admin()
                self.UI_admin.setWindowModality(QtCore.Qt.ApplicationModal)
                self.UI_admin.show()
     
     
     
     
    #############################################################################
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        fen = Fenetre()
        fen.show()
        sys.exit(app.exec_())
    Je vous joins également le constructeur du module dont hérite "App_admin" afin de savoir si le soucis de vient pas d'elle.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    class App_Admin(QtWidgets.QWidget):
        def __init__(self):
     
            super().__init__()
            self.setWindowTitle("test")   
            self.resize(1000,1050)
            self.move(1500,100)
            self.setup_UI()
            self.setStyleSheet("font: 10pt;")
            self.timer_modif()
     
     
        def setup_UI(self):
    En tant qu'autodidacte, si vous avez des conseils, remarques, bonnes pratiques, je suis preneur !
    De même, si vous connaissez de bonnes ressources pour me former correctement au Threading, je suis également preneur ( j'ai passé de nombreuses heures à chercher du contenu clair ).

  2. #2
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 486
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Le throbber faisant partie du graphique, si on exécute une opération longue qui ne se trouve pas dans un thread, il est normal que le graphique se fige.

    Peut-être faut-il identifier l'opération longue en question (interaction avec Outlook pour compter les mails par win32com?), la mettre à part et la placer dans un thread (sans code qui concerne la partie graphique!). Cela ne pose qu'un problème d'architecture du code.

    Dans ce cas, le thread devrait être un "QThread", ce qui lui permettra de signaler au graphique par un signal à créer (.emit()), quand il aura terminé.

  3. #3
    Nouveau membre du Club
    Homme Profil pro
    Assistant aux utilisateurs
    Inscrit en
    Avril 2020
    Messages
    7
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Seine et Marne (Île de France)

    Informations professionnelles :
    Activité : Assistant aux utilisateurs

    Informations forums :
    Inscription : Avril 2020
    Messages : 7
    Par défaut
    Bonjour, et merci pour ta réponse.


    Je commençais déjà à réfléchir à la solution que tu indique de passer le comptage des mails dans un thread, ce serait une solution pour améliorer le lancement du programme mais aussi la stabilité du logiciel après afin de ne pas figer pendant la mise à jour régulière du nombre de mails.

    Cependant, afin que je puisse bien comprendre le threading, un truc m'échappe.

    Dans le code actuel, le lancement du nouvel UI se fait bien dans un thread par ces lignes la, ou je me trompe ?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    self.worker=QtCore.QThread()
            self.worker.start()
     
            self.thread = threading()
            self.thread.moveToThread(self.worker)
            self.thread.close_open_UI_admin()
    Dans ce cas, pourquoi l'UI initiale se fige ?

    Je sais que l'on ne peut pas modifier un élément graphique par le thread (ou alors, avec le jeu d'émission/réception de signal).

    Cependant, dans l'absolu, est ce qu'il est possible de lancer une nouvelle fenêtre par le biais d'un thread ( avec son UI propre ).
    Un peu comme tu le fais dans tes exemples d'ouverture/fermeture de nouvelle fenêtre.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    self.foo=blob()
            self.foo.setWindowModality(QtCore.Qt.ApplicationModal)
            self.foo.show()
            self.close()

  4. #4
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 486
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    J'utilise toujours QThread dans une classe qui en hérite. Je trouve que c'est une solution plus claire et plus adaptable que "moveToThread".

    Voilà un petit code de test qui te dira comment je fais:

    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
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    #!/usr/bin/python3
    # -*- coding: utf-8 -*-
     
    import sys
    import time
    from PyQt5 import (QtWidgets, QtCore)
     
    #############################################################################
    class Operationlongue(QtCore.QThread):
     
        # crée deux nouveaux signaux
        info = QtCore.pyqtSignal(int) # signal pour informer d'une progression
        fini = QtCore.pyqtSignal(bool, list) # signal pour la fin du thread
     
        #========================================================================
        def __init__(self, parent=None):
            super().__init__(parent)
     
            # initialise un drapeau pour signaler une demande d'arrêt
            self.stop = False 
     
        #========================================================================
        def run(self):
            """partie qui s'exécute en tâche de fond
            """
            for i in range(0, 101):
                if self.stop:
                    break # arrêt anticipé demandé
                time.sleep(0.05) # tempo pour ralentir la boucle
                self.info.emit(i) # envoi du signal de progression d'exécution
     
            # # envoi du signal de fin du thread
            # self.stop dira si la fin est normale ou pas
            # et [1,2,3] n'est qu'un exemple de données envoyées par le thread
            self.fini.emit(self.stop, [1,2,3]) 
     
        #========================================================================
        def arreter(self):
            """pour arrêter avant la fin normale d'exécution du thread
            """
            self.stop = True
     
    #############################################################################
    class Fenetre(QtWidgets.QWidget):
     
        #========================================================================
        def __init__(self, parent=None):
            super().__init__(parent)
     
            # bouton de lancement du thread
            self.depart = QtWidgets.QPushButton("Départ", self)
            self.depart.clicked.connect(self.lancement)
     
            # bouton d'arrêt anticipé du thread
            self.arret = QtWidgets.QPushButton("Arrêt", self)
            self.arret.clicked.connect(self.stopthread)
     
            # barre de progression
            self.barre = QtWidgets.QProgressBar(self)
            self.barre.setRange(0, 100)
            self.barre.setValue(0)
     
            # positionne les widgets dans la fenêtre
            posit = QtWidgets.QGridLayout()
            posit.addWidget(self.depart, 0, 0)
            posit.addWidget(self.arret, 1, 0)
            posit.addWidget(self.barre, 2, 0)
            self.setLayout(posit)
     
            # initialise la variable d'instance de classe du thread
            self.operationlongue = None
     
        #========================================================================
        @QtCore.pyqtSlot(bool)
        def lancement(self, ok=False):
            """lance l'opération longue dans le thread
            """
            if self.operationlongue==None or not self.operationlongue.isRunning():
                # initialise la barre de progression
                self.barre.reset()
                self.barre.setRange(0, 100)
                self.barre.setValue(0)
                # initialise l'opération longue dans le thread
                self.operationlongue = Operationlongue()
                # prépare la réception du signal de progression
                self.operationlongue.info.connect(self.progression)
                # prépare la réception du signal de fin
                self.operationlongue.fini.connect(self.finduthread)
                # lance le thread (mais ne l'attend pas avec .join!!!)
                self.operationlongue.start()
     
        #========================================================================
        @QtCore.pyqtSlot(int)
        def progression(self, i):
            """lancé à chaque réception d'info de progression émise par le thread
            """
            self.barre.setValue(i)
     
        #========================================================================
        @QtCore.pyqtSlot(bool)
        def stopthread(self, ok=False):
            """pour arrêter avant la fin
            """
            if self.operationlongue!=None and self.operationlongue.isRunning():
                self.operationlongue.arreter()
     
        #========================================================================
        @QtCore.pyqtSlot(bool, list)
        def finduthread(self, fin_anormale=False, liste=()):
            """Lancé quand le thread se termine
            """
            if fin_anormale:
                # fin anticipée demandée
                QtWidgets.QMessageBox.information(self,
                    "Opération longue",
                    "Arrêt demandé avant la fin!")
            else:
                # fin normale
                self.barre.setValue(100)
                QtWidgets.QMessageBox.information(self,
                    "Opération longue",
                    "Fin normale!")
                # récupération des infos transmises par signal à la fin du thread
                print("Infos données par le thread:", liste)
            # désactive les liens
            self.operationlongue.info.disconnect()
            self.operationlongue.fini.connect 
            # signale quil n'y a plus de thread actif
            self.operationlongue = None
     
        #========================================================================
        def closeEvent(self, event):
            """lancé à la fermeture de la fenêtre quelqu'en soit la méthode
            """
            # si le thread est en cours d'eécution, on l'arrête (brutalement!)
            if self.operationlongue!=None and self.operationlongue.isRunning():
                # désactive les liens
                self.operationlongue.info.disconnect()
                self.operationlongue.fini.connect                        
                # et arrête brutalement le thread
                self.operationlongue.terminate()
            # accepte la fermeture de la fenêtre
            event.accept()
     
    #############################################################################
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
        fen = Fenetre()
        fen.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        fen.show()
        sys.exit(app.exec_())
    Avec ça, tu comprendras mieux que si je dois lancer une partie graphique à la fin du thread, le code de ce lancement se trouvera dans la méthode "finduthread" de la fenêtre graphique, et non dans le thread lui-même.

    NB: évite d'utiliser un nom comme threading qui est un nom de module Python. Et met une majuscule au début des noms de classe.

  5. #5
    Nouveau membre du Club
    Homme Profil pro
    Assistant aux utilisateurs
    Inscrit en
    Avril 2020
    Messages
    7
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Seine et Marne (Île de France)

    Informations professionnelles :
    Activité : Assistant aux utilisateurs

    Informations forums :
    Inscription : Avril 2020
    Messages : 7
    Par défaut
    Désolé pour le délai de réponse, j'ai eu quelques jours de congés.

    Je tenais à te remercier pour tes conseils et cet exemple d'utilisation du Qthread.

    Avec tout ca, je devrai pouvoir améliorer le programme.

  6. #6
    Nouveau membre du Club
    Homme Profil pro
    Assistant aux utilisateurs
    Inscrit en
    Avril 2020
    Messages
    7
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Seine et Marne (Île de France)

    Informations professionnelles :
    Activité : Assistant aux utilisateurs

    Informations forums :
    Inscription : Avril 2020
    Messages : 7
    Par défaut
    Tout marche parfaitement en passant le comptage des mails en threading, et cela a grandement amélioré la rapidité du programme.

    La seule chose qui me bloque encore, c'est que le thread reste en vie même après la fin du programme.

    Les Qthreads n'ayant pas la méthode isDaemon, je ne trouve pas moyen de tuer le thread si le main thread n'existe plus.

    Auriez vous une idée ?

    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
    52
    53
    54
    55
    class App_Direction(QtWidgets.QWidget):
     
        def __init__(self):
            super(App_Direction,self).__init__()
            self.setWindowTitle("test")   
            self.resize(1000,1050)
            self.move(1500,100)
            self.setStyleSheet("font: 10pt;")
            self.feed_stats_label()
     
     
     
        @QtCore.pyqtSlot(int)    
        def feed_stats_label(self):
            self.workthread=WorkerThread()
            self.workthread.mail_sig.connect(self.modif)
     
     
            self.workthread.start()
     
        @QtCore.pyqtSlot(list)
        def modif (self,liste=()):
            print("Je ne recois plus le signal, car je suis mort")
     
    class WorkerThread(QtCore.QThread):
        mail_sig = QtCore.pyqtSignal(list) 
     
     
     
    #========================================================================
        def __init__(self, parent=None):
                super().__init__(parent)
     
    #========================================================================
        def run(self):
     
     
            pythoncom.CoInitialize()
     
            maj=True
            while maj:
                print("Et ca continue encore et encore")
                self.mail_sig.emit([]) # envoi du signal de progression d'exécution
     
                time.sleep(10)
     
     
    if __name__ == '__main__':
     
        QtWidgets.QApplication.setStyle("Fusion")   
        app=QtWidgets.QApplication ([])
     
        win= App_Direction()
        win.show()      
        app.exec_()

Discussions similaires

  1. [Thread] Problème de lancement
    Par michaeljeru dans le forum AWT/Swing
    Réponses: 5
    Dernier message: 06/04/2007, 16h57
  2. [Plugin] Problème de lancement
    Par relivio dans le forum Eclipse Platform
    Réponses: 1
    Dernier message: 03/08/2004, 14h34
  3. Réponses: 6
    Dernier message: 03/03/2004, 14h31
  4. [Kylix] Problème de lancement
    Par jaguarwan dans le forum EDI
    Réponses: 1
    Dernier message: 21/02/2004, 22h40
  5. [Install] Problème de lancement sous debian sid
    Par SoaR245 dans le forum Eclipse Java
    Réponses: 1
    Dernier message: 18/02/2004, 10h01

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