Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

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')
            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)
            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_())
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')
            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
                    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 ?

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.

Calling a thread's quit doesn't automatically stop it, nor its running function. You need to set a flag on the worker and check it periodically (for instance, at every iteration of the for loops) and eventually break or return, and connect the button to a function that will set that flag. – musicamante Mar 10, 2022 at 11:58 I've not tested that code, but I suppose that it "works" just because you're quitting the program: you're not stopping the thread, you're killing it. A proper thread closure calls quit() and wait() (which blocks until the thread actually stops). – musicamante Mar 10, 2022 at 12:23 What do you mean by "if printing doesn't work"? The print statement correctly returns True to isRunning() because setting the flag will not instantly stop the thread: the function needs to return first, meaning that you have to wait for the thread to get control and let the function evaluate the flag and finally return control to the main thread by actually quitting the thread. And, in any case, I don't see any reference to what you're saying in that post: if you're not overriding the QThread's run() (thus ignoring its event loop), a signal will be processed as expected. – musicamante Mar 13, 2022 at 0:10 Setting the flag for the worker means that it will evaluate it as soon as its thread "allows it", then the function will eventually exit, and the thread manager (QThread) will be able to properly quit the thread. After that, wait() is mandatory (otherwise it would be similar to terminate() - which is also discouraged - if you're going to quit before waiting the correct exit of the thread). In any case, remember that QThread (similarly to python Thread) is an interface to the system thread: it causes it's run() (and whatever is called from it) to be executed in a separate thread. – musicamante Mar 13, 2022 at 0:17

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
                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)
    @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())
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_())
                Note: as long as you are sure about types, there's no point in if something == True:: just do if something:. Also, a Close event is implicitly accepted, so you normally only need to do event.ignore().
– musicamante
                Mar 14, 2022 at 2:27
                @musicamante , thanks indeed. Two mor question , I struggled a lot with this trying to use a simple variable as FLAG (before finding the right post that showed me to use a dict)  why that doesnt work ? (I assume answer is  in Python inners). Second I tried to pass the value as a signal too but that didnt work either, but that probably is just because the signal was calling a function that changed my flag and that was running after my looping function ended, Is that possible ?
– pippo1980 ON STRIKE
                Mar 14, 2022 at 9:57
        

Thanks for contributing an answer to Stack Overflow!

  • Please be sure to answer the question. Provide details and share your research!

But avoid

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.