Search code examples
python-3.xpyqtpyqt5qthread

PyQt5 unable to stop/kill/exit from QThread


Borrowing code from : Progress Bar Does not Render Until Job is Complete , I tried to to find way to quit/kill a Qthread while it is working, here my code, you can quit the main window while progress bar is working stopping files to be copied:

import os
import sys
import shutil
from PyQt5 import QtCore, QtWidgets


class myProgressDialog(QtWidgets.QProgressDialog):
    def __init__(self, parent=None):
        super(myProgressDialog, self).__init__(parent=parent)

    def closeEvent(self, event):
       """Get the name of active window about to close
       """
       print('cant close')
       
       event.ignore()


class MainWindow(QtWidgets.QMainWindow):
    startMoveFilesSignal = QtCore.pyqtSignal(str, str)

    def __init__(self):
        super(MainWindow, self).__init__()
        # srcdir = "/media/zachlab/Windows/LinuxStorage/old/embryos"
        # dstdir = "/media/zachlab/Windows/LinuxStorage/old/out"
        
        srcdir = "in"
        dstdir = "out"
        
        self.le_src = QtWidgets.QLineEdit(srcdir)
        self.le_dst = QtWidgets.QLineEdit(dstdir)
        self.button = QtWidgets.QPushButton("Copy")
        # self.button.clicked.connect(self.archiveEntry)
        
        self.button.clicked.connect(self.archiveEntry2)

        central_widget = QtWidgets.QWidget()
        self.setCentralWidget(central_widget)
        lay = QtWidgets.QFormLayout(central_widget)
        lay.addRow("From: ", self.le_src)
        lay.addRow("To: ", self.le_dst)
        lay.addRow(self.button)
        
        print('self,thread :', self.thread)


    def archiveEntry2(self):
        
        print('connected')
        self.progressbar = myProgressDialog(self)
        
        
        # RIMUOVO Cancel Button
        self.progressbar.setCancelButton(None)
        
        self.progressbar.hide()

        self.thread = QtCore.QThread(self)
        self.thread.start()
        self.helper = MoveFileHelper()
        self.startMoveFilesSignal.connect(self.helper.moveFilesWithProgress)
        self.helper.progressChanged.connect(self.progressbar.setValue)
        self.helper.finished.connect(self.on_finished)
        self.helper.started.connect(self.progressbar.show)
        self.helper.errorOccurred.connect(self.on_errorOcurred)
        self.helper.moveToThread(self.thread)
        
        self.archiveEntry()
    
    
    
    ## Questo funziona
    def closeEvent(self, event):
        """Get the name of active window about to close
        """
        print('killing thread')
       
        try:
            if self.thread.isRunning():
               
               
                print('killing running thread', self.thread.isRunning())
               
                # self.thread.terminate()  ## ---------> error Qt has caught an exception thrown from an event handler.
               
                self.thread.quit()  ###  funziona ma non in SPYDER
                

        except Exception as Exceptionz:
            print('Exception :', Exceptionz)
            
            
        try: 
            print('killing running thread after quit :', self.thread.isRunning())
                    
        except:
            print('quitted')
        event.accept()
    
    @QtCore.pyqtSlot()
    def archiveEntry(self):
        self.startMoveFilesSignal.emit(self.le_src.text(), self.le_dst.text())
        self.progressbar.hide()

    @QtCore.pyqtSlot()
    def on_finished(self):
        self.button.setText('Finished')

    @QtCore.pyqtSlot(str)
    def on_errorOcurred(self, msg):
        QtWidgets.QMessageBox.critical(self, "Error Ocurred", msg)
        


class MoveFileHelper(QtCore.QObject):
    progressChanged = QtCore.pyqtSignal(int)
    started = QtCore.pyqtSignal()
    finished = QtCore.pyqtSignal()
    errorOccurred = QtCore.pyqtSignal(str)

    def calculateAndUpdate(self, done, total):
        progress = int(round((done / float(total)) * 100))
        self.progressChanged.emit(progress)

    @staticmethod
    def countFiles(directory):
        count = 0
        if os.path.isdir(directory):
            for path, dirs, filenames in os.walk(directory):
                count += len(filenames)
        return count

    @staticmethod
    def makedirs(dest):
        if not os.path.exists(dest):
            os.makedirs(dest)

    @QtCore.pyqtSlot(str, str)
    def moveFilesWithProgress(self, src, dest):
        numFiles = MoveFileHelper.countFiles(src)
        # if os.path.exists(dest):
        #     self.errorOccurred.emit("Dest exist")
        #     return 
        if numFiles > 0:
            self.started.emit()
            MoveFileHelper.makedirs(dest)
            numCopied = 0
            for path, dirs, filenames in os.walk(src):
                for directory in dirs:
                    destDir = path.replace(src, dest)
                    MoveFileHelper.makedirs(os.path.join(destDir, directory))

                for sfile in filenames:
                    srcFile = os.path.join(path, sfile)
                    destFile = os.path.join(path.replace(src, dest), sfile)
                    shutil.copy(srcFile, destFile)
                    numCopied += 1
                    self.calculateAndUpdate(numCopied, numFiles)
                    for i in range(100000):
                        i = i*10
           
            self.finished.emit()

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    ex = MainWindow()
    ex.resize(640, ex.sizeHint().height())
    ex.show()
    sys.exit(app.exec_())

Not sure if it is the right way to kill a Qthread but seems to work.

Even if after stopping the Qthread: self.thread.quit() I stop the copying of files

but the self.thread.isRunning() still returns True.

When trying to split the code and add another window using:

main.py
import os
import sys
import shutil
from PyQt5 import QtCore, QtWidgets

from mod007b_import import Windowz, MoveFileHelper, myProgressDialog

class MainWindow(QtWidgets.QMainWindow):
    
    def __init__(self):
        super(MainWindow, self).__init__()
        QtWidgets.QMainWindow.__init__(self)
        self.layout = QtWidgets.QHBoxLayout()
        self.lineEdit = QtWidgets.QLineEdit()
        self.lineEdit.setText("Just to fill up the dialog")
        self.layout.addWidget(self.lineEdit)
        self.button = QtWidgets.QPushButton('pppppp')
        self.layout.addWidget(self.button)
    
        self.widget = QtWidgets.QWidget()
        self.widget.setLayout(self.layout)
    
        self.setCentralWidget(self.widget)
        self.setWindowTitle('Simple')

        
        self.button.clicked.connect(self.newWindow)
        
        self.listz = []
         
    def newWindow(self):
        
        print('newwindow')
        
        self.pippo = Windowz()   ########## RIVEDERE PARENT CHILD RELATIONSHIP
        
        self.pippo.show()
        
        # self.listz.append(self.pippo)
        
        pw = self.pippo.parentWidget()
        
        print('list : ', self.listz)
        
        print(pw)
        if pw is not None:
            print('self :', self)
            print('pw : ', pw, pw.layout)
            print('pippo :', self.pippo)
        

        # print(' central_widget :', central_widget, type( central_widget))

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    ex = MainWindow()
    # ex.setWindowTitle('Simple**************')
    ex.resize(640, ex.sizeHint().height())
    ex.show()
    sys.exit(app.exec_())

and

mod007b_import.py
import os
import sys
import shutil
from PyQt5 import QtCore, QtWidgets


class myProgressDialog(QtWidgets.QProgressDialog):
    def __init__(self, parent=None):
        super(myProgressDialog, self).__init__(parent=parent)

    def closeEvent(self, event):
       """Get the name of active window about to close
       """
       print('cant close')
       
       event.ignore()


class Windowz(QtWidgets.QWidget):
# class Windowz(QtWidgets.QMainWindow):
    startMoveFilesSignal = QtCore.pyqtSignal(str, str)

    # def __init__(self,parent=None):
    #     # super(Windowz, self).__init__(parent=parent)
    #     super(Windowz, self).__init__(parent=parent)
    def __init__(self):
        # super(Windowz, self).__init__(parent=parent)
        super().__init__()
        # srcdir = "/media/zachlab/Windows/LinuxStorage/old/embryos"
        # dstdir = "/media/zachlab/Windows/LinuxStorage/old/out"
        
        srcdir = "in"
        dstdir = "out"
        
        self.le_src = QtWidgets.QLineEdit(srcdir)
        self.le_dst = QtWidgets.QLineEdit(dstdir)
        self.button = QtWidgets.QPushButton("Copy")
        # self.button.clicked.connect(self.archiveEntry)
        
        self.button.clicked.connect(self.archiveEntry2)

        
        ### spostati in Main
        # central_widget2 = QtWidgets.QWidget()
        # self.setCentralWidget(central_widget2)
        # lay = QtWidgets.QFormLayout(central_widget2)
        self.lay = QtWidgets.QFormLayout(self)
        self.lay.addRow("From: ", self.le_src)
        self.lay.addRow("To: ", self.le_dst)
        self.lay.addRow(self.button)
        
        

        print('self,thread :', self.thread)
        
        # self.show()

        

    def archiveEntry2(self):
        
        print('connected')
        self.progressbar = myProgressDialog(self)
        
        
        # RIMUOVO Cancel Button
        self.progressbar.setCancelButton(None)
        
        self.progressbar.hide()

        self.thread = QtCore.QThread(self)
        self.thread.start()
        self.helper = MoveFileHelper()
        self.startMoveFilesSignal.connect(self.helper.moveFilesWithProgress)
        self.helper.progressChanged.connect(self.progressbar.setValue)
        self.helper.finished.connect(self.on_finished)
        self.helper.started.connect(self.progressbar.show)
        self.helper.errorOccurred.connect(self.on_errorOcurred)
        self.helper.moveToThread(self.thread)
        
        self.archiveEntry()
    
    
    ## Questo funziona
    def closeEvent(self, event):
        """Get the name of active window about to close
        """
        print('killing thread')
       
        try:
            if self.thread.isRunning():
               
               
                print('killing running thread', self.thread.isRunning())
               
                # self.thread.terminate()  ## ---------> error Qt has caught an exception thrown from an event handler.
               
                self.thread.quit()  ###  doesnt work
                
                # self.progressbar.hide() ### hides the bar 
                
                # self.progressbar.close() ### doesnt work
                
                try: 
                    print('killing running thread after quit :', self.thread.isRunning())
                
                except:
                    print('quitted')
               
        except Exception as Exceptionz:
            print('Exception :', Exceptionz)
        event.accept()
    
    @QtCore.pyqtSlot()
    def archiveEntry(self):
        self.startMoveFilesSignal.emit(self.le_src.text(), self.le_dst.text())
        self.progressbar.hide()

    @QtCore.pyqtSlot()
    def on_finished(self):
        self.button.setText('Finished')

    @QtCore.pyqtSlot(str)
    def on_errorOcurred(self, msg):
        QtWidgets.QMessageBox.critical(self, "Error Ocurred", msg)
        


class MoveFileHelper(QtCore.QObject):
    progressChanged = QtCore.pyqtSignal(int)
    started = QtCore.pyqtSignal()
    finished = QtCore.pyqtSignal()
    errorOccurred = QtCore.pyqtSignal(str)

    def calculateAndUpdate(self, done, total):
        progress = int(round((done / float(total)) * 100))
        self.progressChanged.emit(progress)

    @staticmethod
    def countFiles(directory):
        count = 0
        if os.path.isdir(directory):
            for path, dirs, filenames in os.walk(directory):
                count += len(filenames)
        return count

    @staticmethod
    def makedirs(dest):
        if not os.path.exists(dest):
            os.makedirs(dest)

    @QtCore.pyqtSlot(str, str)
    def moveFilesWithProgress(self, src, dest):
        numFiles = MoveFileHelper.countFiles(src)
        # if os.path.exists(dest):
        #     self.errorOccurred.emit("Dest exist")
        #     return 
        if numFiles > 0:
            self.started.emit()
            MoveFileHelper.makedirs(dest)
            numCopied = 0
            for path, dirs, filenames in os.walk(src):
                for directory in dirs:
                    destDir = path.replace(src, dest)
                    MoveFileHelper.makedirs(os.path.join(destDir, directory))

                for sfile in filenames:
                    srcFile = os.path.join(path, sfile)
                    destFile = os.path.join(path.replace(src, dest), sfile)
                    shutil.copy(srcFile, destFile)
                    numCopied += 1
                    self.calculateAndUpdate(numCopied, numFiles)
                    for i in range(100000):
                        i = i*10
           
            self.finished.emit()

I get a first window, pressing the 'pppppp' button it goes to a second one that is the same as the single file script above: press 'copy' button to start the copying/Qthread, but when I close this window even if the QThread seems to be stopped, progress bar doesnt disappear, I can hide the progress bar but cant close it and in any case the copying process reach completion.

Any idea what is going on ?

PS

in order to have the script working and a having a visible progress bar files need to be in a directory toghether with a 'in' folder with enough files to have a slow process.


Solution

  • OK thanks to @musicamante and to Stopping an infinite loop in a worker thread in PyQt5 the simplest way

    I figured out what was wrong in the first of my two codes, here the first one re-written with a flag set to terminate the copying loop when main window is closed, important

    commenting out or not :

    # self.thread.quit()
                        
    # self.thread.wait()
    

    in Mainwindow def closeEvent(self, event): just after the setting of the flag to True (self.ctrl['break'] = True) will end up having the QThread running / not running before the script terminates anyway. For the flag see the Solution 2: Passing a mutable as a control variable in Stopping an infinite loop in a worker thread in PyQt5 the simplest way

    import os
    import sys
    import shutil
    from PyQt5 import QtCore, QtWidgets
    
    
    class myProgressDialog(QtWidgets.QProgressDialog):
        def __init__(self, parent=None):
            super(myProgressDialog, self).__init__(parent=parent)
           
    
        def closeEvent(self, event):
           """Get the name of active window about to close
           """
           print('cant close')
           
           event.ignore()
    
    
    class MainWindow(QtWidgets.QMainWindow):
        startMoveFilesSignal = QtCore.pyqtSignal(str, str)
    
    
        def __init__(self):
            super(MainWindow, self).__init__()
            # srcdir = "/media/zachlab/Windows/LinuxStorage/old/embryos"
            # dstdir = "/media/zachlab/Windows/LinuxStorage/old/out"
            
            srcdir = "in"
            dstdir = "out"
            
            self.le_src = QtWidgets.QLineEdit(srcdir)
            self.le_dst = QtWidgets.QLineEdit(dstdir)
            self.button = QtWidgets.QPushButton("Copy")
            # self.button.clicked.connect(self.archiveEntry)
            
            self.button.clicked.connect(self.archiveEntry2)
    
            central_widget = QtWidgets.QWidget()
            self.setCentralWidget(central_widget)
            lay = QtWidgets.QFormLayout(central_widget)
            lay.addRow("From: ", self.le_src)
            lay.addRow("To: ", self.le_dst)
            lay.addRow(self.button)
            
            
            self.ctrl = {'break': False} # dict with your control variable
            print('id of ctrl in MainWindow:', id(self.ctrl))
    
    
        def archiveEntry2(self):
            
            print('connected')
            self.progressbar = myProgressDialog(self)
            self.progressbar.setWindowTitle('coopying files')
            
            
            # RIMUOVO Cancel Button
            self.progressbar.setCancelButton(None)
            
            self.progressbar.hide()
    
            self.thread = QtCore.QThread(self)
            self.thread.start()
            self.helper = MoveFileHelper(self.ctrl)
            self.startMoveFilesSignal.connect(self.helper.moveFilesWithProgress)
            
            
            self.helper.progressChanged.connect(self.progressbar.setValue)
            self.helper.finished.connect(self.on_finished)
            self.helper.started.connect(self.progressbar.show)
            self.helper.errorOccurred.connect(self.on_errorOcurred)
            self.helper.moveToThread(self.thread)
            
            self.archiveEntry()
        
        
        
        ## Questo funziona
        def closeEvent(self, event):
                """Get the name of active window about to close
                """
            
                try:
                
                    print('killing thread')
                    
                    print('self.thread.isRunning() before quit :', self.thread.isRunning())
                    
                    if self.thread.isRunning():
                
                        
                        print("quitted   ----> self.ctrl['break'] = True")
                        
                        self.ctrl['break'] = True
                        
                        self.thread.quit()
                        
                        self.thread.wait()
                        
                
                except Exception as Exceptionz:
                    print('Exception :', Exceptionz)
                
            
           
        
                try: 
                    print('self.thread.isRunning() after quit :', self.thread.isRunning())
                        
                except:
                    print('quitted')
                
                
                # event.accept() # not needed implicit
            
                # event.ignore()
            
    
        
        
        @QtCore.pyqtSlot()
        def archiveEntry(self):
            self.startMoveFilesSignal.emit(self.le_src.text(), self.le_dst.text())
            self.progressbar.hide()
    
        @QtCore.pyqtSlot()
        def on_finished(self):
            print('on_finished self.ctrl inside worker : ', self.ctrl)
            print('self.thread.isRunning() after quit on_finished :', self.thread.isRunning())
           
            
     
            
            self.button.setText('Finished')
    
        @QtCore.pyqtSlot(str)
        def on_errorOcurred(self, msg):
            QtWidgets.QMessageBox.critical(self, "Error Ocurred", msg)
            
    
    
    class MoveFileHelper(QtCore.QObject):
        progressChanged = QtCore.pyqtSignal(int)
        started = QtCore.pyqtSignal()
        finished = QtCore.pyqtSignal()
        errorOccurred = QtCore.pyqtSignal(str)
        
        def __init__(self, ctrl):
           
            super().__init__()
        
            self.ctrl = ctrl # dict with your control var
            
            print('self.ctrl inside worker MoveFileHelper : ', self.ctrl)
            
            print('Entered run in worker thread')
            print('id of ctrl in worker:', id(self.ctrl))
            self.ctrl['break'] = False
        
        def calculateAndUpdate(self, done, total):
            progress = int(round((done / float(total)) * 100))
            self.progressChanged.emit(progress)
    
        @staticmethod
        def countFiles(directory):
            count = 0
            if os.path.isdir(directory):
                for path, dirs, filenames in os.walk(directory):
                    count += len(filenames)
            return count
    
        @staticmethod
        def makedirs(dest):
            if not os.path.exists(dest):
                os.makedirs(dest)
                
    
    
    
        @QtCore.pyqtSlot(str, str)
        def moveFilesWithProgress(self, src, dest):
            numFiles = MoveFileHelper.countFiles(src)
            # if os.path.exists(dest):
            #     self.errorOccurred.emit("Dest exist")
            #     return 
            if numFiles > 0:
                self.started.emit()
                MoveFileHelper.makedirs(dest)
                numCopied = 0
                for path, dirs, filenames in os.walk(src):
                    
    
                        for directory in dirs:
                            destDir = path.replace(src, dest)
                            MoveFileHelper.makedirs(os.path.join(destDir, directory))
        
                        for sfile in filenames:
                            
    
                            if self.ctrl['break'] :   # == True : is implicit
                                
                                self.finished.emit()
                                return
                            
                            else:
                                
                                srcFile = os.path.join(path, sfile)
                                destFile = os.path.join(path.replace(src, dest), sfile)
                                shutil.copy(srcFile, destFile)
                                numCopied += 1
                                self.calculateAndUpdate(numCopied, numFiles)
                                for i in range(100000):
                                    i = i*10
    
               
                self.finished.emit()
    
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
        ex = MainWindow()
        ex.resize(640, ex.sizeHint().height())
        ex.show()
        sys.exit(app.exec_())