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 :

Problème avec multiprocessing.Process


Sujet :

Python

  1. #1
    Membre confirmé
    Homme Profil pro
    Lycéen
    Inscrit en
    Février 2010
    Messages
    83
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 31
    Localisation : France

    Informations professionnelles :
    Activité : Lycéen

    Informations forums :
    Inscription : Février 2010
    Messages : 83
    Par défaut Problème avec multiprocessing.Process
    Bonjour.
    J'essaie d'utiliser le module multiprocessing pour que mon application ne plante pas lorsque l'utilisateur essaie de faire un calcul trop long.
    Mais en utilisant Process et Queue, je n'arrive pas à configurer les widgets Tkinter et mes variables.
    print self.q.get()[0] donne bien le résultat attendu, mais quand j'essaye d'utiliser self.q.get()[index], l'application plante.

    Fragment de mon code :
    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
     
    from multiprocessing import Process,Queue
    from Tkinter import *
     
    class Calculatrice:
        def calc(self):
            global entree
     
            entree=Entry(f1,width=20)
            entree.bind('<Return>',startCalc)
            entree.grid()
     
        def evaluate(self,expr):
            """Calcul dans un thread l'expression entrée"""
            c,G,R,Na,k=299792458,6.67300e-11,8.314472,6.02214e23,9e8    # Définition des constantes
     
            try:self.q.put([str(eval(expr)),''])
            except:self.q.put(['',str(exc_info()[1])])
        def startCalc(self,event=""):
            """Lance le calcul dans un thread (processus leger)"""
            global varHist, histCalculs, nCalcul,sauvegarder_hst
     
            if entree.get():    # On ne lance le calcul que si quelque chose a été entré
     
                # Ajout à l'historique des expressions entrées
                histCalculs.append(entree.get())    
                # On réinitialise le champ d'erreur et l'infobulle
                self.label_erreur.configure(text='')    
                self.ib_erreur.label.configure(text='')
                self.ib_erreur.texte=''
                # On désactive le champ de texte pour que l'utilisateur ne puisse pas lancer un calcul pendant un autre calcul
                entree.configure(state='disabled')
                self.boutonCalcul.configure(text='Stop',command=self.stopCalc)
     
                self.q=Queue()
                self.calcul=Process(target=self.evaluate,args=(entree.get(),))
                self.calcul.daemon=True
                self.calcul.start()
     
                entree.configure(state='normal')
                if self.q.get()[0]!='':      # Si aucune erreur n'est survenue pendant le calcul
                    entree.delete(0,'end')
                    entree.insert(0,self.q.get()[0])
                    varHist+=entree.get()+' = '+self.q.get()[0]+'\n'  # Ajout à l'historique
                else:
                    if not self.calcul.is_alive():self.q.put(['','Arrêt du calcul demandé'])
                    varHist+=entree.get()+' = '+self.q.get()[1]+'\n'  # Ajout à l'historique
     
                    self.label_erreur.configure(text=self.q.get()[1],bg='#FF5050')
                    self.label_erreur.after(1000,lambda:self.label_erreur.configure(bg='#D9D9D9'))
     
                    if len(self.q.get()[1])>44:
                        self.ib_erreur.label.configure(text=self.q.get()[1])
                        self.ib_erreur.texte=self.q.get()[1]
     
                nCalcul=len(histCalculs)
                if sauvegarder_hst.get()==1:Historique().update_hst()
     
                self.boutonCalcul.configure(text='Calculer',command=self.startCalc)
     
                self.calcul.join()
    Pourquoi ça ne marche pas ?
    Merci pour votre aide.

  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,

    C'est trop difficile de déverminer un code comme ça, surtout qu'il n'est pas suffisamment complet pour s'exécuter par un simple copier-coller, aussi je te propose un code qui marche en guise de source d'inspiration. Ce sera probablement la méthode que je retiendrai pour la prochaine version de ma Calculext:

    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
     
    #!/usr/bin/python
    # -*- coding: utf-8 -*-
    from __future__ import division
     
    import sys
    from time import sleep
    from multiprocessing import Process, Manager
    from Tkinter import *
     
    def evaluate(expression, resultat):
        """Calcule dans un processus l'expression et renvoie le résultat 
             resultat[0] renvoie False si erreur de calcul
             resultat[1] renvoie le resultat ou l'erreur selon resultat[0]
        """
        try:
            res = "%r" % eval(expression)
            ok = True
        except:
            res ="%s" % sys.exc_info()[1]
            ok = False
        resultat[0] = ok
        resultat[1] = res
     
    class Calculatrice(Frame):
        def __init__(self, master=None):
            Frame.__init__(self, master)
            self.grid()
            self.entree = Entry(self, width=20)
            self.entree.bind('<Return>', self.startCalc)
            self.entree.grid()
            self.entree.grid(row=0)
            self.stop = Button(self, text="Stop", command=self.stopCalc)
            self.stop.grid(row=1)
            self.entree.focus_set()
     
        def startCalc(self,event=""):
            """Lance le calcul dans un processus"""
            if len(self.entree.get().strip()) != 0:    
                # On ne lance le calcul que si l'entrée n'est pas vide
                self.arretdemande = False
                self.resultat = Manager().dict()
                self.resultat[0] = None
                self.expression = self.entree.get()
                self.calcul = Process(target=evaluate, args=(self.expression, self.resultat))
                self.calcul.start()
                self.after_idle(self.affiche)
     
        def affiche(self):
            # affiche le résultat s'il est disponible
            if not self.calcul.is_alive():
                # affichage du résultat
                if self.arretdemande:
                    # si arrêt demandé, il ne faut surtout pas questionner le résultat!
                    self.entree.delete(0,END)
                    self.entree.insert(0,"Arrêt demandé")
                else:    
                    # afficher le résultat ou l'erreur
                    if self.resultat[0]:
                        # pas d'erreur de calcul => afficher le résultat
                        self.entree.delete(0, END)
                        self.entree.insert(0, self.resultat[1])
                    else:
                        # afficher l'erreur de calcul
                        self.entree.delete(0, END)
                        self.entree.insert(0, "Erreur: " + self.resultat[1])
            else:
                self.after_idle(self.affiche)
     
        def stopCalc(self):
            if self.calcul.is_alive():
                self.calcul.terminate()
                self.arretdemande = True
                while self.calcul.is_alive():
                    sleep(0.1)
     
    ##############################################################################
    # lancement de la calculatrice
    #
    if __name__ == "__main__":
        fen=Tk()
        app=Calculatrice(fen)
        fen.mainloop()
    J'ai préféré utiliser le partage d'un dictionnaire par Manager() au lieu de Queue().

    Le plus difficile de la synchronisation entre la fenêtre tkinter et le process de calcul est que:

    - le graphique doit retrouver la main tout de suite après le lancement du calcul sinon, le bouton pour arrêter le calcul ne marchera pas. (donc un 'start', mais pas de 'join')

    - mais une fois que le calcul est terminé, et donc le processus de calcul arrêté, le graphique doit s'en apercevoir (comment?) pour afficher le résultat!

    La solution choisie ici est de créer une méthode 'affiche', et de demander, grâce à 'after_idle', que cette méthode soit lancée quand il n'y a plus d'évènement à traiter. Mais comme ce lancement n'a lieu qu'une fois, il faut relancer cette demande à la fin de la méthode 'affiche'. Mais, bien sûr, il n'y a affichage que si le processus de calcul est vraiment arrêté (self.calcul.is_alive()==False).

    Un simple test de calcul long, par exemple 999999**999999, montre que le clic sur le bouton stop arrête effectivement le calcul.

    Tyrtamos

  3. #3
    Membre confirmé
    Homme Profil pro
    Lycéen
    Inscrit en
    Février 2010
    Messages
    83
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 31
    Localisation : France

    Informations professionnelles :
    Activité : Lycéen

    Informations forums :
    Inscription : Février 2010
    Messages : 83
    Par défaut
    Merci.
    Cela fonctionne.

    Je n'ai pas compris pourquoi la fonction evaluate est créée hors de la classe Calculatrice, mais je l'ai intégré dans la méthode startCalc() :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     def startCalc(self,event=""):
            """Lance le calcul dans un processus"""
            def evaluate(expression, resultat):
                """Calcule dans un processus l'expression et renvoie le résultat
                resultat[0] renvoie False si erreur de calcul
                resultat[1] renvoie le resultat ou l'erreur selon resultat[0]"""
    Et quel est le rôle de time.sleep(0.1) ? Je l'ai retiré, et le calcul se fait bien et peut être tué sans message d'erreur ni temps d'attente.

    Merci encore.

  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,

    Citation Envoyé par Spitfire 95 Voir le message
    Je n'ai pas compris pourquoi la fonction evaluate est créée hors de la classe Calculatrice, mais je l'ai intégré dans la méthode startCalc()
    J'ai appris, à mes dépends, que les bibliothèques graphiques ne faisaient pas toujours bon ménage avec les threads et les processus. Or, comme tu as vu, pour moi la classe Calculatrice EST la fenêtre graphique. Je préfère donc mettre à part tout ce qui est thread et processus.

    Et puis, c'est bien que la structure du code reflète les différentes fonctions très différentes à remplir. Ici, c'est plutôt une bonne pratique qui permet de mieux comprendre le code 6 mois plus tard.

    Tu as essayé de l'intégrer, et ça marche: tant mieux. En cas de problème, tu sauras ce qu'il faut essayer...

    Citation Envoyé par Spitfire 95 Voir le message
    Et quel est le rôle de time.sleep(0.1) ? Je l'ai retiré, et le calcul se fait bien et peut être tué sans message d'erreur ni temps d'attente
    C'est simplement une habitude que j'ai prise: la méthode qui arrête le processus ne se termine que quand le processus est effectivement arrêté. Mais ici, on peut s'en passer.


    Je profite de ce message pour insister sur un point: terminate est une méthode brutale pour arrêter un processus, et quand on l'a utilisée, il faut se garder de questionner les variables partagées qui peuvent contenir n'importe quoi (dixit la doc). D'où ma variable 'arretdemande' qui permet de savoir lors de l'affichage que le processus ne s'est pas arrêté normalement.

    Tyrtamos

Discussions similaires

  1. Problème avec System.Diagnostics.Process.Start();
    Par ZeProgrammator dans le forum ASP.NET
    Réponses: 13
    Dernier message: 06/11/2009, 10h55
  2. Problème avec un Process
    Par Arnaud_B dans le forum VB.NET
    Réponses: 4
    Dernier message: 01/10/2009, 13h39
  3. Réponses: 2
    Dernier message: 18/08/2009, 16h44
  4. Réponses: 2
    Dernier message: 11/02/2009, 23h30
  5. Réponses: 10
    Dernier message: 01/03/2007, 12h39

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