PYQt5:QThread:在线程仍然 运行 时被销毁(实例分配为 QDialog )

PYQt5: QThread: Destroyed while thread is still running ( with instance assigned as QDialog )

我遇到了这个问题 QThread:当我关闭 NextWindow QDialog.

时线程仍然 运行 时被破坏

我写的代码是

import sys
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtGui as qtg
from PyQt5 import QtCore as qtc

from pathlib import Path
from hashlib import sha1


def recursive_hashes(path):
    """Generate name and SHA1 hash of all files under the given path"""
    if path.is_file():
        sha1_obj = sha1()
        try:
            with open(path, 'rb') as handle:
                while True:
                    data = handle.read(8192)
                    if not data:
                        break
                    sha1_obj.update(data)
            sha1_hash = sha1_obj.hexdigest()
        except PermissionError:
            sha1_hash = 'Permission Denied'
        yield (str(path), sha1_hash)
    elif path.is_dir():
        try:
            for child in path.iterdir():
                yield from recursive_hashes(child)
        except PermissionError:
            yield(str(path), 'Permission Denied')
    else:
        yield (str(path), 'Not a file or dir')

class Worker(qtc.QObject):

    hashed = qtc.pyqtSignal(str, str)

    @qtc.pyqtSlot(str)
    def hash_directory(self, root):
        hash_gen = recursive_hashes(Path(root))
        for path, sha1_hash in hash_gen:
            self.hashed.emit(path, sha1_hash)

class NextWindow(qtw.QDialog):

    hash_requested = qtc.pyqtSignal(str)

    def __init__(self):
        """MainWindow constructor."""
        super().__init__()
        layout = qtw.QFormLayout()
        self.setLayout(layout)

        self.file_root = qtw.QLineEdit(returnPressed=self.start_hashing)
        self.go_button = qtw.QPushButton('Start Hashing', clicked=self.start_hashing)
        self.results = qtw.QTableWidget(0, 2)
        self.results.setHorizontalHeaderLabels(['Name', 'SHA1-sum'])
        self.results.horizontalHeader().setSectionResizeMode(qtw.QHeaderView.Stretch)
        self.results.setSizePolicy(qtw.QSizePolicy.Expanding, qtw.QSizePolicy.Expanding)

        layout.addRow(qtw.QLabel('SHA1 Recursive Hasher'))
        layout.addRow('File Root', self.file_root)
        layout.addRow('', self.go_button)
        layout.addRow(self.results)

        # Create a worker object and a thread
        self.worker = Worker()
        self.worker_thread = qtc.QThread(self)
        self.worker.hashed.connect(self.add_hash_to_table)
        self.hash_requested.connect(self.worker.hash_directory)

        # Assign the worker to the thread and start the thread
        self.worker.moveToThread(self.worker_thread)
        self.worker_thread.start()

        # Connect signals & slots AFTER moving the object to the thread


        # End main UI code
        self.show()

    def add_hash_to_table(self, name, sha1_sum):
        """Add the given name and sha1 sum to the table"""

        row = self.results.rowCount()
        self.results.insertRow(row)
        self.results.setItem(row, 0, qtw.QTableWidgetItem(name))
        self.results.setItem(row, 1, qtw.QTableWidgetItem(sha1_sum))
        print(name, sha1_sum)

    def start_hashing(self):
        """Prepare the GUI and emit the hash_requested signal"""

        # Clear the table
        while self.results.rowCount() > 0:
            self.results.removeRow(0)

        # Get the file root and validate it
        file_root = self.file_root.text()
        if not Path(file_root).exists():
            qtw.QMessageBox.critical(self, 'Invalid Path', 'The specified file root does not exist.')
            return

        # Emit the signal
        self.hash_requested.emit(file_root)
        #self.worker.hash_directory(file_root)

class MainWindow(qtw.QDialog):
    def __init__(self):
        super().__init__()
        layout = qtw.QFormLayout()
        
        self.next_button = qtw.QPushButton('Next', clicked=self.next_button)
        layout.addWidget(self.next_button)
        self.setLayout(layout)
    
    def next_button(self):
        dialog = NextWindow()
        dialog.exec_()
        
if __name__ == '__main__':
    app = qtw.QApplication(sys.argv)
    mw = MainWindow()
    mw.show()
    sys.exit(app.exec())

我查看了 running and QThread: Destroyed while thread is still running中的参考答案。

答案基本上没有像

那样将实例分配给QThread

self.thread = QThread(parent=self) 虽然我这样做了,但问题仍然存在。

QThread: Destroyed while thread is still 运行 的调试消息可能无处不在,但原因可能并不总是相同的。对于这种情况,即使您将 self 包含在

中,当您关闭对话框时线程也没有很好地结束
self.worker_thread = qtc.QThread(self)

在这种情况下,您应该拒绝问题中提到的 Qthread Terminating QThread gracefully on QDialog reject()

所以您应该在代码中再添加一行

self.worker_thread.start()
self.rejected.connect(self.thread.terminate)

根据 PYQT5 document

When the thread is terminated, all threads waiting for the thread to finish will be woken up.

Warning: This function is dangerous and its use is discouraged. The thread can be terminated at any point in its code path. Threads can be terminated while modifying data. There is no chance for the thread to clean up after itself, unlock any held mutexes, etc. In short, use this function only if absolutely necessary.

所以我建议添加

self.rejected.connect(self.thread.quit)

self.rejected.connect(self.thread.exit)