PyQt5 无法从 QThread stop/kill/exit

PyQt5 unable to stop/kill/exit from QThread

借用代码,我试图在它工作时找到 quit/kill Qthread 的方法,这里是我的代码,你可以在进程中退出主 window栏正在停止要复制的文件:

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

不确定这是否是杀死 Qthread 的正确方法,但似乎有效。

即使在停止 Qthread 后:self.thread.quit() 我停止复制文件

但是 self.thread.isRunning() 仍然 returns True

尝试拆分代码并添加另一个 window 时使用:

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

我得到第一个 window,按 'pppppp' 按钮它会转到第二个,与上面的单个文件脚本相同:按 'copy' 按钮启动copying/Qthread,但是当我关闭这个 window 即使 QThread 似乎停止了,进度条也不会消失,我可以隐藏进度条但无法关闭它,无论如何复制过程都会完成。

知道发生了什么吗?

PS

为了使脚本正常工作并具有可见的进度条,文件需要与 'in' 文件夹一起放在一个目录中,该文件夹具有足够的文件以使进程缓慢。

好的,感谢@musicamante 和 Stopping an infinite loop in a worker thread in PyQt5 the simplest way

我弄清楚了我的两个代码中的第一个有什么问题,这里是第一个 re-written 设置了标志以在 main window 关闭时终止复制循环,重要

评论与否:

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

in Mainwindow def closeEvent(self, event): 标志设置为 True (self.ctrl['break'] = True) 后 QThread 运行 / not 运行 在脚本终止之前。对于标志,请参阅 解决方案 2:将可变变量作为控制变量传递 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_())