манипулирование виджетом в PySide2 QThread приводит к тому, что python3 не отвечает - PullRequest
0 голосов
/ 11 марта 2019

То, что я пытаюсь сделать, это обновить QTreeWidget другим потоком, чтобы не зависать пользовательский интерфейс.Поэтому я делаю демонстрацию следующим образом:

Среда: win10 x64, Python3.6.8 x32, PySide2 5.12.1

# -*- coding: utf-8 -*-
import os

from PySide2 import QtWidgets, QtGui, QtCore


class ShowFolderTreeThread(QtCore.QThread):

    def __init__(self, p, treeWidget: QtWidgets.QTreeWidget, root_dir: str = "."):
        super().__init__(p)
        self.root_dir = root_dir
        self.treeWidget = treeWidget

    def list_folder(self, parent_path: str, parent_item=None, max_depth: int = 3):
        if max_depth <= 0:
            return
        try:
            for content in os.listdir(parent_path):
                absolute_path = os.path.join(parent_path, content)
                is_dir: bool = os.path.isdir(absolute_path)
                item = QtWidgets.QTreeWidgetItem(parent_item or self.treeWidget)
                item.setText(0, content)
                item.setText(1, "Folder" if is_dir else os.path.splitext(content)[1])
                if is_dir:
                    self.list_folder(absolute_path, item, max_depth - 1)
        except:
            pass

    def run(self):
        try:
            self.treeWidget.clear()
            self.list_folder(self.root_dir)
        except Exception as e:
            print(type(e), e)


class MainWindow(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.treeWidget = QtWidgets.QTreeWidget(self)
        self.treeWidget.setHeaderLabel("name")
        self.pushButton = QtWidgets.QPushButton(self)
        self.pushButton.setObjectName("pushButton")
        self.pushButton.setText("Refresh")
        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.treeWidget)
        layout.addWidget(self.pushButton)
        self.setLayout(layout)
        self.resize(400, 600)
        self.workThread = ShowFolderTreeThread(self, self.treeWidget)
        self.workThread.setObjectName("workThread")

        self.workThread.finished.connect(self.thread_finished)
        self.pushButton.clicked.connect(self.start_thread)

    def start_thread(self):
        self.pushButton.setDisabled(True)  # fixme: cause Python not responding if set btn to disabled and then enabled
        self.workThread.start()

    def thread_finished(self):
        self.pushButton.setEnabled(True)  # fixme: cause Python not responding if set btn to disabled and then enabled
        print("thread_finished")


if __name__ == '__main__':
    import sys

    try:
        app = QtWidgets.QApplication(sys.argv)
        w = MainWindow()
        w.show()
        sys.exit(app.exec_())
    except Exception as e:
        print(type(e), e)
        raise e

Все работает нормально, за исключением нескольких щелчков кнопки,Появится диалоговое окно «Python not response», и программа завершит работу с кодом выхода -1073741819 (0xC0000005).Кто-то говорит, что этот код выхода является исключением из нулевого указателя.

Явление исчезает, если я закомментирую следующие строки:

line57: self.pushButton.setDisabled(True)
line61: self.pushButton.setEnabled(True)

Я подозреваю, что кнопка ref вызывает это.Поэтому я попытаюсь проверить это еще раз: оставьте строки выше и закомментируйте следующие строки:

line21: item = QtWidgets.QTreeWidgetItem(parent_item or self.treeWidget)
line22: item.setText(0, content)
line23: item.setText(1, "Folder" if is_dir else os.path.splitext(content)[1])

Феномен также исчезает.Кто-нибудь знает, что не так с моим кодом Python?

Редактировать 1: удалить весь код пользовательского интерфейса в QThread, все еще не работает.ошибка терминала:

Fatal Python error: GC object already tracked

Процесс завершен с кодом выхода 255

# -*- coding: utf-8 -*-
import os

from PySide2 import QtWidgets, QtGui, QtCore


class ShowFolderTreeThread(QtCore.QThread):
    addTopItem = QtCore.Signal(str, str, tuple)

    def __init__(self, p, root_dir: str = "."):
        super().__init__(p)
        self.root_dir = root_dir

    def list_folder(self, parent_path: str, max_depth: int = 3, parent_item_ids=tuple()):
        if max_depth <= 0:
            return
        try:
            for row, content in enumerate(os.listdir(parent_path)):
                content_path = os.path.join(parent_path, content)
                is_dir: bool = os.path.isdir(content_path)
                self.addTopItem.emit(content, "Folder" if is_dir else os.path.splitext(content)[1], parent_item_ids)
                if is_dir:
                    del is_dir
                    self.list_folder(content_path, max_depth - 1, (*parent_item_ids, row))
        except Exception as e:
            print("list_folder", type(e), e)

    def run(self):
        try:
            self.list_folder(self.root_dir)
        except Exception as e:
            print(type(e), e)


class MainWindow(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.treeWidget = QtWidgets.QTreeWidget(self)
        self.treeWidget.setColumnCount(2)
        self.treeWidget.setHeaderLabels(["name", "type", ])
        self.treeWidget.setColumnWidth(0, 250)

        self.pushButton = QtWidgets.QPushButton(self)
        self.pushButton.setText("&Refresh")

        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.treeWidget)
        layout.addWidget(self.pushButton)
        self.setLayout(layout)

        self.resize(400, 600)

        home = os.path.expanduser('~')
        self.workThread = ShowFolderTreeThread(self, home)

        self.workThread.finished.connect(self.thread_finished)
        self.workThread.addTopItem.connect(self.addTopItem)
        self.pushButton.clicked.connect(self.start_thread)

        self.timer = QtCore.QTimer()
        self.timer.setInterval(10)
        self.timer.setSingleShot(True)
        self.timer.timeout.connect(self.pushButton.click)
        self.timer.start()

    def addTopItem(self, content: str, content_type: str, parent_item_ids: tuple):
        item = QtWidgets.QTreeWidgetItem()
        item.setText(0, content)
        item.setText(1, content_type)
        if parent_item_ids:
            index, *item_ids = parent_item_ids
            parent_item = self.treeWidget.topLevelItem(index)
            for i in item_ids:
                parent_item = parent_item.child(i)
            parent_item.addChild(item)
        else:
            self.treeWidget.addTopLevelItem(item)

    def start_thread(self):
        self.pushButton.setDisabled(True)
        self.treeWidget.clear()
        self.workThread.start()

    def thread_finished(self):
        self.pushButton.setEnabled(True)
        self.timer.start()

    def closeEvent(self, event: QtGui.QCloseEvent):
        self.timer.stop()
        super().closeEvent(event)


if __name__ == '__main__':
    import sys

    try:
        app = QtWidgets.QApplication(sys.argv)
        w = MainWindow()
        w.show()
        sys.exit(app.exec_())
    except Exception as e:
        print(type(e), e)
        raise e
...