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

Python Discussion :

Implémentation de Asyncio, bloque la GUI


Sujet :

Python

  1. #1
    Membre actif
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Octobre 2013
    Messages
    156
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Santé

    Informations forums :
    Inscription : Octobre 2013
    Messages : 156
    Points : 218
    Points
    218
    Par défaut Implémentation de Asyncio, bloque la GUI
    Bonjour,

    Je suis en train de replacer dans une application Python3.4.3 & PyQt5 un QThread par des opérations asynchrones a l'aide a la librairie asyncio.
    Pour le moment ce que j'ai fait fonctionne, mais par contre la GUI reste bloquée pendant les traitements.
    J'avoue aussi ne pas avoir complétement compris le "asyncio.sleep", en gros ce que j'en comprend c'est que c'est sensé relâcher l'event loop, durant un temps donné mais c'est tout.

    Voici mon 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
     
    class TestsController:
     
        def got_result(self, callbac):
            print("ok")
     
        @log_method('log_tests')
        def run_worker(self, list_test):
            self.worker = TestsWorker(self.get_env(), list_test)
     
            self.loop = asyncio.get_event_loop()
            task = self.loop.create_task(self.worker.run_tests())
            task.add_done_callback(self.got_result)
            self.loop.run_until_complete(task)
            # self.loop.close()
     
     
    class TestWorkerStatus(Enum):
        """Enumeration describing the current test thread status."""
     
        running = 0
        paused = 1
        stoped = 2
     
     
    class TestsWorker(object):
     
        def __init__(self, env, test_list):
            self.env = env
            self.test_list = test_list
     
        @asyncio.coroutine
        def run_tests(self):
            logging.getLogger('log_tests').info('Running tests ... ')
            current_test = None
            application_state.status = _("Running tests ...")
            self.tests_worker_status = TestWorkerStatus.running
     
            for item in self.test_list:
                self.current_test_successful = False
                while not self.current_test_successful:
                    # ... Handle other status
                    if self.tests_worker_status == TestWorkerStatus.running:
                        if isinstance(item, File):
                            print("will run next test")
                            results = yield from self._run_test(item)
                            print("results : ", results)
                            # .... Handle result 
                            self.current_test_successful = True
            application_state.status = _("Tests finished ...")
            signal_manager.tests_finished.emit()
     
        @asyncio.coroutine
        def _run_test(self, item):
            current_test = None
            try:
                if isinstance(item, File):
                    module = SourceFileLoader(item.name, item.path).load_module()
                    test = module.Test(self.env)
                    current_test = test
                    self.update_test_progression(self.test_list, item, test._name)
                    results = test.test()   #  < --- This is the long process 
                    return results
            except TestException as test_ex:
                    ... Exception handling
            except Exception as ex:
                current_test.add_result(False, "Exception : {0}".format(ex))
                self._handle_exception(ex, ex, current_test._result)
                return
     
        @log_method('log_tests')
        def update_test_progression(self, list_test, test, test_name):
            try:
                progression = str(list_test.index(test) + 1) + "/" + str(len(list_test))
                text = _("Test : {0},  Name : {1} ").format(progression, test_name)
                application_state.status = text
            except:
                raise
     
        @log_method('log_tests')
        def _handle_exception(self, exception, exception_text, current_test_result):
            try:
                signal_manager.result_test_received.emit(current_test_result)
                logging.getLogger('log_tests').error(exception)
                signal_manager.error_occured.emit(str(exception_text))
                application_state.status = _("An error occured during last test. : {0}").format(exception)
                signal_manager.test_exception_occured.emit()
                signal_manager.tests_finished.emit()
            except:
                raise
    Voila comment je lance mes traitements. Ceux-ci sont exécuté. Le processus long se trouve dans la classe "Test" que je n'ai pas encore touché.
    La classe test hérite de "TestCase", celle-ci contient quelques méthodes utiles aux tests, mais n'utilisent pas asyncio.
    De plus, la classe "Test" peu faire appel à d'autres API qui ne sont pas asynchrone.

    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
     
    from components.test_api.test_case import TestCase
    from components.test_api.tests_fixtures import Fixtures
     
    class Test(TestCase):
        """Generic test class."""
     
        def __init__(self, environement):
            """Initializer."""
            super(Test, self).__init__(environement)
            self._category = _("01 Software control")
            self._name = _("Cleaning EEPROM")
            self._index = 1
            self.results_names = [_("01 - Reset EEPROM")]
            self.available_in = ["production"]
     
        @Fixtures.inject_cpu_api
        def test(self):
            """Generic test method."""
            rez = self.cmd.clear_eeprom()
            value = rez == ['ANS_GENERAL', 'OK']
            return self.add_result(self.results_names[0], value, _("Reset EEPROM"))
    Comment faire en sorte que ce code ne soit plus bloquant ?

  2. #2
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 283
    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 283
    Points : 36 770
    Points
    36 770
    Par défaut
    Salut,

    Citation Envoyé par IPreferCSharp Voir le message
    Je suis en train de replacer dans une application Python3.4.3 & PyQt5 un QThread par des opérations asynchrones a l'aide a la librairie asyncio.
    Pour le moment ce que j'ai fait fonctionne, mais par contre la GUI reste bloquée pendant les traitements.
    Un GUI et asyncio sont des mécaniques de multi-tâche coopératifs où vous avez une file d'attente de callbacks lancés un après l'autre par un event-loop.
    Et c'est "coopératif" parce qu'on suppose que chaque callback s'exécute assez rapidement pour que çà reste fluide car vous ne savez pas "pre-empter" l'exécution d'un callback
    (la pre-emption est ce qu'apportent les threads).
    Si vous avez ces deux mécaniques dans un même threads vous pouvez essayer de lancer un peu l'un, puis l'autre,... i.e. créer une super event loop "au dessus" ou les laisser s’exécuter dans des threads séparées.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  3. #3
    Membre actif
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Octobre 2013
    Messages
    156
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Santé

    Informations forums :
    Inscription : Octobre 2013
    Messages : 156
    Points : 218
    Points
    218
    Par défaut
    Citation Envoyé par wiztricks Voir le message
    Salut,



    Un GUI et asyncio sont des mécaniques de multi-tâche coopératifs où vous avez une file d'attente de callbacks lancés un après l'autre par un event-loop.
    Et c'est "coopératif" parce qu'on suppose que chaque callback s'exécute assez rapidement pour que çà reste fluide car vous ne savez pas "pre-empter" l'exécution d'un callback
    (la pre-emption est ce qu'apportent les threads).
    Si vous avez ces deux mécaniques dans un même threads vous pouvez essayer de lancer un peu l'un, puis l'autre,... i.e. créer une super event loop "au dessus" ou les laisser s’exécuter dans des threads séparées.

    - W
    J'ai pas compris

  4. #4
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 461
    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 461
    Points : 9 248
    Points
    9 248
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Citation Envoyé par IPreferCSharp Voir le message
    J'ai pas compris
    Il y a 2 boucles de traitement des évènements. Quand l'une s'exécute, l'autre attend...

    Ce n'est pas impossible, mais il faut définir à quel moment ils se synchronisent.

    Une question: en quoi QThread était insuffisant?
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  5. #5
    Membre actif
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Octobre 2013
    Messages
    156
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Santé

    Informations forums :
    Inscription : Octobre 2013
    Messages : 156
    Points : 218
    Points
    218
    Par défaut
    Citation Envoyé par tyrtamos Voir le message
    Bonjour,

    Une question: en quoi QThread était insuffisant?
    QThread n'était pas vraiment insuffisant, cependant pour une raison encore obscure, il crash sans aucun message d'erreur, rien du tout. J'ai hook les messages d'erreur de python, et de Qt mais toujours rien.
    J'ai donc essayé de voir pour changé mon QThread par un thread normal. Mais j'avais utilisé un "QEventLoop" pour mettre en pause mon QThread quand j'en ai besoin. Je ne sais pas vraiment comment faire la même chose avec un Thread python. C'est pourquoi aujourd'hui je regarde ce que ça donnerai avec asyncio.

    Autrement dit pour ces deux boucles, il va falloir que je revoie la manière dont je synchronise ma GUI avec mes données, si je veux implémenté asyncio ?

  6. #6
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 283
    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 283
    Points : 36 770
    Points
    36 770
    Par défaut
    Citation Envoyé par IPreferCSharp Voir le message
    Autrement dit pour ces deux boucles, il va falloir que je revoie la manière dont je synchronise ma GUI avec mes données, si je veux implémenté asyncio ?
    Ben ouais... En fait, vous pourriez faire des recherches sur Internet et vous inspirez de ce qui a déjà été fait avant de vous lancer...
    Mais pour ce qui est de votre problème initial, le mieux serait de commencer par récupérer les données sans threads, puis d'ajouter le thread, puis d'ajouter le GUI pour voir ce qu'il se passe i.e. vous assurez que chacun des morceaux de votre construction fonctionnent correctement avant de tout mélanger et ne plus savoir ou donner de la tête.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  7. #7
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 461
    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 461
    Points : 9 248
    Points
    9 248
    Billets dans le blog
    6
    Par défaut
    Citation Envoyé par IPreferCSharp Voir le message
    ...pour une raison encore obscure, il crash sans aucun message d'erreur, rien du tout. J'ai hook les messages d'erreur de python, et de Qt mais toujours rien.
    S'il y a quelque chose dont j'ai horreur, c'est bien ça: un arrêt brutal du programme sans aucun message! Et j'ai trouvé une différence importante sur ce point en passant de PyQt4 à PyQt5. Avec ce dernier, ça plante plus souvent sans rien dire.

    J'ai d'autant plus le problème que je lance souvent mes programmes en exe avec cx_freeze, et que je ne suis donc pas en console.

    Alors, j'utilise un petit complément pour me donner un minimum d'infos en cas d'erreur critique, et ça marche assez bien.

    Le principe est simple: on crée une fonction (ici: messagederreur(msgtyp, context, msgerr)), qu'on installe avec qInstallMessageHandler(messagederreur).

    Voilà un petit programme en PyQt5 qui permet de simuler le déclenchement d'une erreur critique, et qui fait apparaître la petite fenêtre d'info.

    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
    #!/usr/bin/python
    # -*- coding: utf-8 -*-
    # Python 3
     
    import sys
    from PyQt5 import QtCore, QtWidgets
     
    #############################################################################
    class Fenetre(QtWidgets.QWidget):
     
        def __init__(self, parent=None):
            super().__init__(parent)
            self.resize(300, 200)
     
            self.bouton = QtWidgets.QPushButton("Déclencher une erreur critique!", self)
            self.bouton.clicked.connect(self.crash)
     
            posit = QtWidgets.QGridLayout()
            posit.addWidget(self.bouton, 0, 0)
            self.setLayout(posit)
     
        def crash(self, b):
            #x = 1/0
            QtCore.qCritical("Probleme!!!")
     
    #############################################################################
    def messagederreur(msgtyp, context, msgerr):
        """affiche les messages critiques dans une fenêtre
           mise en place par: QtCore.qInstallMessageHandler(messagederreur)
        """
        global app
        if msgtyp in [QtCore.QtCriticalMsg, QtCore.QtFatalMsg, QtCore.QtSystemMsg]:
            # affichage en console
            print("Critical/fatal/système PyQt5: %s\n" % (msgerr,))
            # affichage graphique
            QtWidgets.QMessageBox.critical(None, 
                "Erreur critique PyQt5",
                "Critical/fatal/system: {}\n".format(msgerr) +\
                "Fichier: {}\n".format(context.file) +\
                "Ligne: {}\n".format(context.line) +\
                "Fonction: {}\n".format(context.function)
                )
            app.quit() # arrêt optionnel mais conseillé!
        else:
            # autres messages pour la console
            print("Info PyQt5: {}\n".format(msgerr))
     
    #############################################################################
    if __name__ == "__main__":
     
        # lancement de la bibliothèque graphique
        app = QtWidgets.QApplication(sys.argv)
     
        # Pour afficher les erreurs fatales de PyQt5
        QtCore.qInstallMessageHandler(messagederreur)
     
        # lancement de la fenêtre
        fen = Fenetre()
        fen.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        fen.show()
     
        # lancement de la boucle de traitement des évènements
        sys.exit(app.exec_())
    Mais je suis loin d'avoir tout résolu sur ce problème de récupération et d'affichage des erreurs!
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  8. #8
    Membre actif
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Octobre 2013
    Messages
    156
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Santé

    Informations forums :
    Inscription : Octobre 2013
    Messages : 156
    Points : 218
    Points
    218
    Par défaut
    C'est effectivement, ce que j'ai fait aussi, mais même avec ça j'ai pas d'erreur :
    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
     
    def on_exception_triggered(type_except, value, tb):
        import traceback
        trace = "".join(traceback.format_exception(type_except, value, tb))
        print("ERROOOOOOR", trace)
        sys.__excepthook__(type_except, value, tb)
    sys.excepthook = on_exception_triggered
     
    from PyQt5.QtCore import qInstallMessageHandler, QMessageLogContext
    from PyQt5.Qt import QtMsgType
     
     
    def myQtMsgHandler(msg_type, msg_log_context, msg_string):
        print('file:', msg_log_context.file)
        print('function:', msg_log_context.function)
        print('line:', msg_log_context.line)
        print('  txt:', msg_string)
    qInstallMessageHandler(myQtMsgHandler)
    Du coup j'ai réessayé le QThread,en rajoutant des print() à chaque fonction dans lesquelles mon process passe et avec ça, ça crash pas ... Je vais continué d'investiguer dans ce sens, et si vraiment je trouve pas la solution, je verrai pour revoir mon archi, mais je préfère éviter

  9. #9
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 283
    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 283
    Points : 36 770
    Points
    36 770
    Par défaut
    Citation Envoyé par IPreferCSharp Voir le message
    C'est effectivement, ce que j'ai fait aussi, mais même avec ça j'ai pas d'erreur :
    Vous attrapez les messages d'erreurs qui remontent jusqu'à Python.
    La mouture de Tyrtamos attrape ceux de Qt qui ne remontent peut être pas si haut.
    note: si vous avez un problème d'allocation mémoire, le code qui plantera sera sans doute une victime collatérale.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  10. #10
    Membre actif
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Octobre 2013
    Messages
    156
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Santé

    Informations forums :
    Inscription : Octobre 2013
    Messages : 156
    Points : 218
    Points
    218
    Par défaut
    il me semble que la seconde partie de mon code catch bien les erreurs de Qt, avec qInstallMessageHandler(myQtMsgHandler)

  11. #11
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 283
    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 283
    Points : 36 770
    Points
    36 770
    Par défaut
    Citation Envoyé par IPreferCSharp Voir le message
    il me semble que la seconde partie de mon code catch bien les erreurs de Qt, avec qInstallMessageHandler(myQtMsgHandler)
    Ah oui désolé.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

Discussions similaires

  1. Réponses: 0
    Dernier message: 26/06/2010, 19h21
  2. Moteur physique : comment l'implémenter ?
    Par haypo dans le forum Algorithmes et structures de données
    Réponses: 15
    Dernier message: 17/12/2003, 12h56
  3. [GUI] Ou trouver les standard ?
    Par Braim dans le forum Windows
    Réponses: 5
    Dernier message: 01/10/2003, 08h13
  4. Réponses: 2
    Dernier message: 06/07/2002, 12h36
  5. Implémentation des fonctions mathématiques
    Par mat.M dans le forum Mathématiques
    Réponses: 9
    Dernier message: 17/06/2002, 16h19

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