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 :

[Pyside6] QProcess dans un QThread


Sujet :

PyQt Python

  1. #1
    Membre actif Avatar de FadeToBlack
    Homme Profil pro
    ...
    Inscrit en
    Août 2010
    Messages
    314
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : ...
    Secteur : Associations - ONG

    Informations forums :
    Inscription : Août 2010
    Messages : 314
    Points : 204
    Points
    204
    Par défaut [Pyside6] QProcess dans un QThread
    Bonjour à tous,

    J'ai une question piège pour vous. Malheureusement, je ne pourrais pas vous montrer de code car je n'ai pas accès à mon ordi de bureau aujourd'hui. Mais je pense que la théorie suffit.

    Voici la situation :

    J'ai, actuellement, une mainWindow qui lance un Qprocess. Ce QProcess lance la commande Windows X-COPY pour faire des copies de fichiers d'un répertoire à un autre.

    J'ai mis une progressBar et un compteur afin de visualiser l'avancée de la copie.

    Tout fonctionne bien si je copie plusieurs dizaines de milliers de fichiers de petites tailles. Mais si les fichiers dépassent les 100Mo, cela ne va plus. Mon compteur et la progressBar se figent, tout comme l'app, jusqu'à la fin de la copie.

    J'ai donc pensé créer un QthreadPool à partir de ma mainWindow. Et dans ce second Qthread, je voulais lancer un QProcess pour la copie.

    Le tout étant relié via des Signaux et des Slots pour gérer la QProgressBar et le compteur qui reste dans la mainWindow.

    Est-ce que cela vous parait envisageable ou faut-il que je procède autrement.


    JE vous remercie pour vos réflexions et votre aide.

    Bonne journée à tous

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

    Le problème est que pour faire marcher une barre de progression, il faut un indicateur d'avancement. Pour l'instant, il n'y a que le passage d'une copie de fichier au suivant. Et à ma connaissance, xcopy ne renvoie pas d'indicateur d'avancement en court de copie (à vérifier, mais s'il existait, il faudrait voir comment le récupérer dans le programme graphique).

    La seule solution est donc d'utiliser un autre programme de copie qui serait capable de renvoyer un indicateur de progression.
    On peut même le créer sous Python! J'avais fait un petit code qui faisait cela justement pour avoir une progression:

    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
    def copiefichier(ficsource, ficdestin, lgbuf=8192, callback=None):
        """Copie le contenu du fichier source vers le fichier destination
        """
        lgcop = 0  # portera le nb d'octets copiés pendant la copie
        lgtot = os.path.getsize(ficsource)  # nb total d'octets à copier
     
        if os.path.isdir(ficdestin):
            # la destination étant un répertoire, on lui ajoute le nom source
            ficdestin = os.path.join(ficdestin, os.path.basename(ficsource))
     
        with open(ficsource, 'rb') as fs:
            with open(ficdestin, 'wb') as fd:
                while True:
                    buf = fs.read(lgbuf)
                    if len(buf) == 0:
                        break  # plus rien à copier: on a fini
                    fd.write(buf)
                    if callback != None:
                        lgcop += len(buf)
                        callback(lgcop, lgtot)  # envoi des infos au callback si demandé
    On voit que à chaque bloc binaire copié, un appel est fait au callback avec le nb d'octets copiés et le nb d'octets à copier.

    Peut-être faut-il adapter la taille du buffer à la taille du fichier?

    Il faudrait mettre ça dans un QThread pour ne pas figer le graphique, et permettre de renseigner la barre de progression par un envoi de signaux.

    On pourrait même avoir 2 barres de progression comme ça existe dans certains programmes:
    - une barre de progression qui indique les fichiers déjà copiés par rapport au nb total de fichiers à copier
    - une barre de progression qui indique la progression de la copie de chaque fichier.

    Ce serait intéressant de voir avant tout ça si la performance de ce programme de copie Python est suffisante par rapport à xcopy.

  3. #3
    Membre actif Avatar de FadeToBlack
    Homme Profil pro
    ...
    Inscrit en
    Août 2010
    Messages
    314
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : ...
    Secteur : Associations - ONG

    Informations forums :
    Inscription : Août 2010
    Messages : 314
    Points : 204
    Points
    204
    Par défaut
    Bonjour Tyrtamos,

    X-copy renvoie bien les éléments copiés au fur et à mesure que les copies se font. Question performance, j'arrive à copiés 100 Go de fichiers de 50Mo en moins de 30 mn. Ce qui me suffit.

    Quoiqu'il en soit, via mon Qprocess je récupère les éléments de retour de x-copy et à chaque retour j'incrémente un label.

    Comme, je le disais, cela fonctionne parfaitement avec des fichiers de petites tailles mais à partir de quelques méga cela freeze.

    Je poserai mon code lundi, si cela vous dit de voir ce que j'ai pu faire.

  4. #4
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 480
    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 480
    Points : 9 277
    Points
    9 277
    Billets dans le blog
    6
    Par défaut
    Citation Envoyé par FadeToBlack Voir le message
    Je poserai mon code lundi, si cela vous dit de voir ce que j'ai pu faire.
    J'ai regardé la doc de xcopy, et je ne vois pas comment avoir une progression de la copie d'un fichier. Donc, oui, ça m'intéresse!

    Par ailleurs, robocopy est considéré comme plus performant que xcopy.

  5. #5
    Membre actif Avatar de FadeToBlack
    Homme Profil pro
    ...
    Inscrit en
    Août 2010
    Messages
    314
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : ...
    Secteur : Associations - ONG

    Informations forums :
    Inscription : Août 2010
    Messages : 314
    Points : 204
    Points
    204
    Par défaut
    Salut Tyrtamos,

    Voici la partie Qprocess de mon script.

    Ce qui gère le retour du powerShell, et donc de la méthode Xcopy se trouve dans la méthode.

    Si tu as des remarques, sujections pour mon petit problème.

    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
     
    import sys
    from PySide6.QtWidgets import Qdonlication, QLabel, QWidget, QPushButton, \
        QHBoxLayout,QVBoxLayout, QProgressBar, QMessageBox, QLineEdit, QMenuBar
    from PySide6.QtGui import QAction
     
    from PySide6.QtCore import QDirIterator, QDir, QProcess, Signal, Qt, QCoredonlication
    from don.services.tools  import change_path
     
    class copy_data(QWidget):
     
        copyFinished = Signal(object)
        def __init__(self, don):
            super().__init__()
            self.setWindowTitle("Tree Copy")
            self.resize(500,400)
            self.don = don
            self.data = {
                "nbDir":    0,
                "nbFiles":  0,
                "size":     0
            }
     
            self.script_xcopy = "xcopy '" + change_path(self.don['origin']) +"\*' " + change_path(self.don["working"]) + " /s"
            print(self.don['origin'])
            print(self.don['working'])
            print(self.script_xcopy)
            #-----------
     
            self.copiedFiles = 0
     
            self.init_ui()
     
        def init_ui(self):
            self.initialiseData()
            self.repLabel = QLabel(f'Dir to copy: {self.data["nbDir"]}')
            self.fileLabel = QLabel(f'File to copy: {self.data["nbFiles"]}')
            self.sizeLabel = QLabel()
            if self.data['size'] >= 10**9 :
                self.sizeLabel.setText(f'Taille de la copie: {self.data["size"]/1024/1024/1024:.2f} Go')
            else:
                self.sizeLabel.setText(f'Taille de la copie: {self.data["size"]/1024/1024:.2f} Mo')
     
            self.copiedFilesLEdit = QLabel()
     
            self.pb = QProgressBar()
            self.confirm = QPushButton('Confirm')
            self.annul   = QPushButton('Stop')
            self.annul.setEnabled(0)
     
            hb = QHBoxLayout()
            hb.addStretch(1)
            hb.addWidget(self.confirm)
            hb.addWidget(self.annul)
     
     
            vb = QVBoxLayout()
            vb.addWidget(self.repLabel)
            vb.addWidget(self.fileLabel)
            vb.addWidget(self.sizeLabel)
            vb.addStretch()
            vb.addWidget(self.pb)
            vb.addWidget(self.copiedFilesLEdit)
            vb.addLayout(hb)
     
            self.setLayout(vb)
            self.confirm.clicked.connect(self.ps_process)
            self.setWindowFlags(Qt.FramelessWindowHint)
     
     
        def initialiseData(self):
     
            rep = QDirIterator(self.don['origin'], QDir.NoDotAndDotDot | QDir.AllDirs, QDirIterator.Subdirectories )
            files = QDirIterator(self.don['origin'],  QDir.NoDotAndDotDot | QDir.Files, QDirIterator.Subdirectories )
     
     
            while files.hasNext():
                files.next()
                info = files.fileInfo()
                nom = info.fileName()
                chemin = info.filePath()
                self.data["size"] += info.size()
                self.data["nbFiles"] += 1
     
            while rep.hasNext():
                rep.next()
                self.data["nbDir"] += 1
     
     
        def ps_process(self):
            self.confirm.setEnabled(0)
            self.annul.setEnabled(1)
     
            self.powershell_process = QProcess()
     
            self.powershell_process.readyReadStandardOutput.connect(self.powershell_process_stdout)
            self.powershell_process.readyReadStandardError.connect(self.powershell_process_stderr)
            self.powershell_process.finished.connect(self.cleanup_powershell_process)
            self.powershell_process.start("powershell.exe", [self.script_xcopy])
            self.powershell_process.waitForFinished(-1)
     
     
        def powershell_process_stderr(self):
            err = bytes(self.powershell_process.readAllStandardError()).decode("cp858")
            pass
     
        def powershell_process_stdout(self):
            self.result = bytes(self.powershell_process.readAllStandardOutput()).decode("cp858")
            print(self.result)
            countFiles = self.result.split("\r\n")[:-1]
            for countFile in countFiles:
                self.copiedFiles +=1
            self.pb.setValue(self.copiedFiles/self.data["nbFiles"]*100)
            self.copiedFilesLEdit.setText(f'{self.copiedFiles} for {self.data["nbFiles"]}')
     
        def cleanup_powershell_process(self):
            self.powershell_process = None
            self.copyFinished.emit(self.data)
            self.close()

    Bonne journée à toi

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

    Si vous écrivez "self.powershell_process.waitForFinished(-1)", le GUI se gèle en attendant que le Process se termine.

    - W

  7. #7
    Membre actif Avatar de FadeToBlack
    Homme Profil pro
    ...
    Inscrit en
    Août 2010
    Messages
    314
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : ...
    Secteur : Associations - ONG

    Informations forums :
    Inscription : Août 2010
    Messages : 314
    Points : 204
    Points
    204
    Par défaut
    Bonjour,

    si je ne considère pas le self.powershell_process.waitForFinished(-1), le GUI gèle au bout de quelques minutes si le volume de fichiers est important.

    En fait, cela ne me gêne pas que le GUI gèle et je le comprends. Du coup, c'est le but de ma question. Puis-je à partir de mon GUI principal, lancer un QthreadPool, qui lancera lui-même un QProcess ?

    La mise à jour de mon GUI principal se ferait donc via des Signaux et Slots à partir du thread secondaire. (Enfin c'est ce que j'ai compris ......).

    Mais du coup, si je stoppe ou mets en pause le thread secondaire, est-ce que cela stoppe ou met en pause le QProcess ?

    Bonne journée

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

    Citation Envoyé par FadeToBlack Voir le message
    Mais du coup, si je stoppe ou mets en pause le thread secondaire, est-ce que cela stoppe ou met en pause le QProcess ?
    Threads et Process sont des objets systèmes.
    QThread/QProcess sont des "classes" interface qui permettent de les utiliser (par le programmeur). Si on détruit l'objet QThread/QProcess est ce que l'entité système associée sera détruite? Déjà, il faut regarder ce que raconte la documentation puis on teste.

    Citation Envoyé par FadeToBlack Voir le message
    ]En fait, cela ne me gêne pas que le GUI gèle et je le comprends. Du coup, c'est le but de ma question. Puis-je à partir de mon GUI principal, lancer un QthreadPool, qui lancera lui-même un QProcess ?
    Un QThreadPool gère des threads qui... pourraient lancer des QProcess attendant qu'ils se terminent. Pas sûr de comprendre l'intérêt d'avoir autant de threads que de process en dessous... D'autant que les QProcess étant asynchrones, ils ne bloquent pas le GUI et peuvent être gérés directement par celui ci.

    Cela étant, c'est possible...

    - W

  9. #9
    Membre actif Avatar de FadeToBlack
    Homme Profil pro
    ...
    Inscrit en
    Août 2010
    Messages
    314
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : ...
    Secteur : Associations - ONG

    Informations forums :
    Inscription : Août 2010
    Messages : 314
    Points : 204
    Points
    204
    Par défaut
    @wiztricks,

    En fait je ne veux pas lancer des Thread différents avec des QPcoress à l'intérieur. J'essaie de trouver une solution au gèle de mon interface, lorsque je lance une copie de fichiers allant de 50 à 100 Mo pour un poids totale de plus de 100 Go.

    D'après ce que j'ai pu glaner comme info, il était recommandé d'appeler un thread spécifique qui gérerait la copie de façon autonome. Le GUI resterait accessible via les signaux envoyés. Maintenant, je ne suis absolument pas spécialiste de PySide/PyQt.
    j'ai peut être faut une erreur d'interprétation de la doc et de ce que j'ai trouvé sur le net.

    Pour ce qui est de "self.powershell_process.waitForFinished(-1)", j'ai opté pour un délai illimité car je ne sais pas si je dois copier 100 Go ou 10To. Cela me paraissait là encore la meilleure option. Néanmoins, en refaisant le test ce matin, avec une valeur correspondant à 1h en msec, j'ai le même gèle du GUI.

    Merci pour votre aide.

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

    Citation Envoyé par FadeToBlack Voir le message
    D'après ce que j'ai pu glaner comme info, il était recommandé d'appeler un thread spécifique qui gérerait la copie de façon autonome
    Le thread spécifique c'est bien lorsque la copy est réalisée depuis le programme Python. Là, vous lancez une commande externe pour faire la copie. Difficile d'éviter le QProcess pour la lancer, mais le thread ne sert plus à rien (les 2 sont asynchrones et évitent le gel du GUI).

    Pour ce qui est de "self.powershell_process.waitForFinished(-1)", j'ai opté pour un délai illimité car je ne sais pas si je dois copier 100 Go ou 10To.
    Que ce soit lancer un thread ou un process, ce sera fait depuis le thread principal (celui du GUI). Une fois lancé, si depuis ce thread, on dit "attend que çà se termine", çà attend (puisqu'on le demande) et l'interface utilisateur est gelée.
    Normalement, on peut connecter "finished" qui sera appelé lorsque ce sera terminé (pour faire le ménage et/ou gérer le nombre de Process parallèles) et du coup on ne bloque pas le thread principal.

    - W

  11. #11
    Membre actif Avatar de FadeToBlack
    Homme Profil pro
    ...
    Inscrit en
    Août 2010
    Messages
    314
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : ...
    Secteur : Associations - ONG

    Informations forums :
    Inscription : Août 2010
    Messages : 314
    Points : 204
    Points
    204
    Par défaut
    Bonjour,

    Merci à tous pour vos conseils. J'ai appliqué un QThread à mon appli qui appelle un QProcess qui lui même appelle un Xcopy de windows.

    Sur 300 000 fichiers pour 2To, je n'ai plus aucun gèle de l'appli.

    Je vous remercie tous.

    Bonne journée

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. PySide6 - StyleSheet dans fichier .qrc
    Par FadeToBlack dans le forum PyQt
    Réponses: 0
    Dernier message: 21/06/2021, 05h27
  2. Réponses: 1
    Dernier message: 19/02/2014, 23h19
  3. Problème d'appel d'un slot dans un QThread
    Par petitclem dans le forum Multithreading
    Réponses: 9
    Dernier message: 20/03/2012, 09h51
  4. [QThread] Interception de std::signal dans un QThread
    Par uriotcea dans le forum Multithreading
    Réponses: 0
    Dernier message: 01/08/2010, 11h55
  5. [QThread] QProcess et QSemaphore
    Par slymira dans le forum Multithreading
    Réponses: 11
    Dernier message: 29/11/2007, 11h40

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