PyQt5桌面应用开发(12):QFile与线程安全

PyQt5桌面应用系列

segment fault

一个可爱的PyQt5程序,但有有点瑕疵……

editor

点击左边的文件树中的文件时,就会打开文件,读入内容并设置到中间的代码编辑器控件中。程序启动时,自动读入本文对应的python源程序。

self.editor.setPlainText(read_file("segfaults_pyqt5.py"))

第一次读入时,一切正常,文件正常载入和显示。但是当点击文件读入时间触发后,程序会自动退出,显示段错误 (核心已转储)

这究竟是怎么回事呢?真相只有一个,我们再来完整看看源代码。

import time
from PyQt5.QtCore import Qt, QDir, QModelIndex, QFile, QIODevice
from PyQt5.QtGui import QIcon, QTextDocument
from PyQt5.QtWidgets import QApplication, QTreeWidgetItem, QToolBar, QStatusBar, QStyle, QFormLayout, QLineEdit
from PyQt5.QtWidgets import QDockWidget
from PyQt5.QtWidgets import QMainWindow, QWidget, QMenuBar, QTreeWidget

from code_editor import QCodeEditor
from py_syntax_highlighter import PythonHighlighter

def read_file(filename) -> str:
    return open(filename, 'r', encoding="utf-8").read()

class MyMainWindow(QMainWindow):
    def __init__(self):
        super(MyMainWindow, self).__init__()
        self.setWindowIcon(QIcon('icon.png'))
        self.setWindowTitle('PyQt5 Editor')
        self.editor = QCodeEditor(display_line_numbers=True,
                                  highlight_current_line=True,
                                  syntax_high_lighter=PythonHighlighter)
        self.setCentralWidget(self.editor)

        # 第一次读入文件:OK
        self.editor.setPlainText(read_file("segfaults_pyqt5.py"))
        self._set_menubar()
        self._set_toolbar()
        self._set_statusbar()
        self._set_left_dock()
        self._set_right_dock()

    def _set_menubar(self: QMainWindow):
        mb = QMenuBar(parent=self)

        file_menu = mb.addMenu('&File')
        for a in ['&New', '&Open', '&Save', '&Save as']:
            file_menu.addAction(QIcon("icon.png"), a, self.close)
        file_menu.addSeparator()
        file_menu.addAction(self.style().standardIcon(QStyle.SP_DialogCloseButton), '&Quit', self.close)

        self.setMenuBar(mb)

    def _set_toolbar(self: QMainWindow):
        tb = QToolBar(self)

        for a in ['New', 'Open', 'Save', 'Save as']:
            tb.addAction(QIcon("icon.png"), a, self.close)
        tb.addSeparator()
        tb.addAction(self.style().standardIcon(QStyle.SP_DialogCloseButton), "Close", self.close)
        tb.setToolButtonStyle(Qt.ToolButtonFollowStyle)
        # tb.setMovable(False)

        self.addToolBar(tb)

    def _set_statusbar(self: QMainWindow):
        sb = QStatusBar(self)
        sb.showMessage("Hello!")

        self.setStatusBar(sb)

    def _set_left_dock(self: QMainWindow):
        dock = QDockWidget('Project structure', self)
        tv = QTreeWidget(dock)
        tv.setColumnCount(4)
        # tv.setHeaderHidden(True)
        tv.setHeaderLabels(['', '', 'name', 'path'])

        root = self._get_file_tree(".", tv)

        root.setExpanded(True)

        tv.addTopLevelItem(root)

        dock.setWidget(tv)

        dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
        dock.setFeatures(QDockWidget.DockWidgetMovable)
        self.addDockWidget(Qt.LeftDockWidgetArea, dock)

        self.tv = tv
        self.tv.clicked.connect(self.load_item)

    def load_item(self, index: QModelIndex):
        item = self.tv.itemFromIndex(index)
        fn = item.text(3)
        if QDir(fn).exists():
            return

        if fn.endswith(".py"):
            try:
                #####################################################
                # 触发点击时间后,调用到这里出问题
                # 段错误:到底是什么原因呢?
                self.editor.setPlainText(read_file(fn))   
                #####################################################             
                doc: QTextDocument = self.editor.document()
                self.statusBar().showMessage(f"Open file {fn}, length: {doc.characterCount()}.")
            except Exception as e:
                print(e)

    def _set_right_dock(self: QMainWindow):

        dock = QDockWidget('File information', self)
        info = QWidget(dock)
        layout = QFormLayout(info)

        self.line_edit = QLineEdit(f"{self.editor.document().blockCount()}")
        self.line_edit.setEnabled(False)
        self.character_edit = QLineEdit(f"{self.editor.document().characterCount()}")
        self.character_edit.setEnabled(False)
        layout.addRow("Block count", self.line_edit)
        layout.addRow("Character count", self.character_edit)

        info.setLayout(layout)

        dock.setWidget(info)
        dock.setFeatures(QDockWidget.DockWidgetMovable)
        self.addDockWidget(Qt.RightDockWidgetArea, dock)

        doc: QTextDocument = self.editor.document()
        doc.blockCountChanged.connect(lambda count: self.line_edit.setText(f"{count}"))
        doc.contentsChanged.connect(lambda: self.character_edit.setText(f"{doc.characterCount()}"))

    def _get_file_tree(self, root_dir, parent) -> QTreeWidgetItem:

        d = QDir(QDir(root_dir).absolutePath())
        item = QTreeWidgetItem(parent)
        item.setIcon(1, self.style().standardIcon(QStyle.SP_DirIcon))
        item.setText(2, d.dirName())
        item.setText(3, d.canonicalPath())

        for f in d.entryInfoList(QDir.NoDot | QDir.NoDotDot | QDir.Dirs):
            if f.isDir():
                self._get_file_tree(f.canonicalFilePath(), item)

        for f in d.entryInfoList(["*.py"], QDir.NoDot | QDir.NoDotDot | QDir.Files):
            fi = QTreeWidgetItem(item)
            fi.setText(2, f.fileName())
            fi.setText(3, f.canonicalFilePath())
            if f.fileName().endswith("py"):
                fi.setIcon(1, QIcon("icon.png"))

        return item


if __name__ == '__main__':
    import sys

    app = QApplication(sys.argv)
    app.setStyleSheet("QPlainTextEdit {background-color: cyan} ")
    main_window = MyMainWindow()

    main_window.setMinimumSize(1440, 768)
    main_window.show()
    sys.exit(app.exec_())

gdb backtrace

这个时候,就可以用上调试工具gdb了。

gdb -ex r -ex "thread apply all backtrace" --args python segfaults_pyqt5.py

点击图形界面中左边的文件,引发段错误,打印出的跟踪信息如下,一共三个线程QDBusConnection,QXcbEventQueue和python。在python线程的#9行,我们可以看到是meth_QPlainTextEdit_setPlainText (),出问题的调用看来就是这里。

Thread 3 (Thread 0x7fffe8969640 (LWP 60056) "QDBusConnection"):
#0  0x00007ffff7d5bd7f in __GI___poll (fds=0x7fffe4004ed0, nfds=3, timeout=-1) at ../sysdeps/unix/sysv/linux/poll.c:29
#1  0x00007ffff6146666 in ?? () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
#2  0x00007ffff60ef3e3 in g_main_context_iteration () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
#3  0x00007ffff66f91cc in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Core.so.5
#4  0x00007ffff669c21a in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Core.so.5
#5  0x00007ffff64b2844 in QThread::exec() () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Core.so.5
#6  0x00007ffff1215fd5 in QDBusConnectionManager::run() () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/plugins/platforms/../../lib/libQt5DBus.so.5
#7  0x00007ffff64b3b35 in QThreadPrivate::start(void*) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Core.so.5
#8  0x00007ffff7cd7b43 in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:442
#9  0x00007ffff7d69a00 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81

Thread 2 (Thread 0x7ffff10c1640 (LWP 60055) "QXcbEventQueue"):
#0  0x00007ffff7d5bd7f in __GI___poll (fds=0x7ffff10c0ca8, nfds=1, timeout=-1) at ../sysdeps/unix/sysv/linux/poll.c:29
#1  0x00007ffff5be27e2 in ?? () from /lib/x86_64-linux-gnu/libxcb.so.1
#2  0x00007ffff5be422c in xcb_wait_for_event () from /lib/x86_64-linux-gnu/libxcb.so.1
#3  0x00007ffff16647b0 in QXcbEventQueue::run() () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/plugins/platforms/../../lib/libQt5XcbQpa.so.5
#4  0x00007ffff64b3b35 in QThreadPrivate::start(void*) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Core.so.5
#5  0x00007ffff7cd7b43 in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:442
#6  0x00007ffff7d69a00 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81


Thread 1 (Thread 0x7ffff7c42000 (LWP 60046) "python"):
#0  0x00000000004fd33c in _PyEval_EvalFrameDefault ()
#1  0x0000000000531823 in _PyFunction_Vectorcall ()
#2  0x00007ffff6f630b0 in PyQtSlot::call(_object*, _object*) const () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/QtCore.abi3.so
#3  0x00007ffff6f63558 in PyQtSlot::invoke(void**, _object*, void*, bool) const () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/QtCore.abi3.so
#4  0x00007ffff6f6384e in PyQtSlotProxy::unislot(void**) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/QtCore.abi3.so
#5  0x00007ffff6f64317 in PyQtSlotProxy::qt_metacall(QMetaObject::Call, int, void**) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/QtCore.abi3.so
#6  0x00007ffff66d5f97 in void doActivate<false>(QObject*, int, void**) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Core.so.5
#7  0x00007ffff3287cec in QTextDocumentPrivate::finishEdit() () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Gui.so.5
#8  0x00007ffff21364fb in QWidgetTextControlPrivate::setContent(Qt::TextFormat, QString const&, QTextDocument*) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Widgets.so.5
#9  0x00007ffff29f78ff in meth_QPlainTextEdit_setPlainText () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/QtWidgets.abi3.so
#10 0x000000000051ad90 in ?? ()
#11 0x00000000004e75dc in _PyObject_MakeTpCall ()
#12 0x00000000004fb152 in _PyEval_EvalFrameDefault ()
#13 0x000000000055e097 in ?? ()
#14 0x000000000055c852 in ?? ()
#15 0x00007ffff6f630b0 in PyQtSlot::call(_object*, _object*) const () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/QtCore.abi3.so
#16 0x00007ffff6f63558 in PyQtSlot::invoke(void**, _object*, void*, bool) const () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/QtCore.abi3.so
#17 0x00007ffff6f6384e in PyQtSlotProxy::unislot(void**) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/QtCore.abi3.so
#18 0x00007ffff6f64317 in PyQtSlotProxy::qt_metacall(QMetaObject::Call, int, void**) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/QtCore.abi3.so
#19 0x00007ffff66d5f97 in void doActivate<false>(QObject*, int, void**) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Core.so.5
#20 0x00007ffff21a2105 in QAbstractItemView::clicked(QModelIndex const&) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Widgets.so.5
#21 0x00007ffff21a5013 in QAbstractItemView::mouseReleaseEvent(QMouseEvent*) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Widgets.so.5
#22 0x00007ffff2215be3 in QTreeView::mouseReleaseEvent(QMouseEvent*) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Widgets.so.5
#23 0x00007ffff2b637db in sipQTreeWidget::mouseReleaseEvent(QMouseEvent*) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/QtWidgets.abi3.so
#24 0x00007ffff1fa1740 in QWidget::event(QEvent*) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Widgets.so.5
#25 0x00007ffff204a21e in QFrame::event(QEvent*) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Widgets.so.5
#26 0x00007ffff21ae2bc in QAbstractItemView::viewportEvent(QEvent*) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Widgets.so.5
#27 0x00007ffff221564c in QTreeView::viewportEvent(QEvent*) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Widgets.so.5
#28 0x00007ffff2b6614b in sipQTreeWidget::viewportEvent(QEvent*) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/QtWidgets.abi3.so
#29 0x00007ffff669d5a0 in QCoreApplicationPrivate::sendThroughObjectEventFilters(QObject*, QEvent*) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Core.so.5
#30 0x00007ffff1f63412 in QApplicationPrivate::notify_helper(QObject*, QEvent*) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Widgets.so.5
#31 0x00007ffff1f6a1f8 in QApplication::notify(QObject*, QEvent*) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Widgets.so.5
#32 0x00007ffff2c1e0d6 in sipQApplication::notify(QObject*, QEvent*) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/QtWidgets.abi3.so
#33 0x00007ffff669d808 in QCoreApplication::notifyInternal2(QObject*, QEvent*) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Core.so.5
#34 0x00007ffff1f6953a in QApplicationPrivate::sendMouseEvent(QWidget*, QMouseEvent*, QWidget*, QWidget*, QWidget**, QPointer<QWidget>&, bool, bool) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Widgets.so.5
#35 0x00007ffff1fbafe8 in QWidgetWindow::handleMouseEvent(QMouseEvent*) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Widgets.so.5
#36 0x00007ffff1fbdcf3 in QWidgetWindow::event(QEvent*) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Widgets.so.5
#37 0x00007ffff1f6343c in QApplicationPrivate::notify_helper(QObject*, QEvent*) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Widgets.so.5
#38 0x00007ffff1f69f20 in QApplication::notify(QObject*, QEvent*) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Widgets.so.5
#39 0x00007ffff2c1e0d6 in sipQApplication::notify(QObject*, QEvent*) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/QtWidgets.abi3.so
#40 0x00007ffff669d808 in QCoreApplication::notifyInternal2(QObject*, QEvent*) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Core.so.5
#41 0x00007ffff316f56d in QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::MouseEvent*) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Gui.so.5
#42 0x00007ffff3170955 in QGuiApplicationPrivate::processWindowSystemEvent(QWindowSystemInterfacePrivate::WindowSystemEvent*) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Gui.so.5
#43 0x00007ffff314c8ab in QWindowSystemInterface::sendWindowSystemEvents(QFlags<QEventLoop::ProcessEventsFlag>) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Gui.so.5
#44 0x00007ffff166569a in xcbSourceDispatch(_GSource*, int (*)(void*), void*) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/plugins/platforms/../../lib/libQt5XcbQpa.so.5
#45 0x00007ffff60f1d3b in g_main_context_dispatch () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
#46 0x00007ffff61466c8 in ?? () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
#47 0x00007ffff60ef3e3 in g_main_context_iteration () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
#48 0x00007ffff66f91cc in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Core.so.5
#49 0x00007ffff669c21a in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Core.so.5
#50 0x00007ffff66a51d3 in QCoreApplication::exec() () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/Qt5/lib/libQt5Core.so.5
#51 0x00007ffff2a69be1 in meth_QApplication_exec_ () from /home/qchen/pyqt5-writing/.venv/lib/python3.11/site-packages/PyQt5/QtWidgets.abi3.so
#52 0x000000000051ad90 in ?? ()
#53 0x00000000004e75dc in _PyObject_MakeTpCall ()
#54 0x00000000004fb152 in _PyEval_EvalFrameDefault ()
#55 0x000000000062e1b4 in ?? ()
#56 0x00000000004f3a67 in PyEval_EvalCode ()
#57 0x0000000000647c37 in ?? ()
#58 0x0000000000645350 in ?? ()
#59 0x0000000000650d15 in ?? ()
#60 0x0000000000650a64 in _PyRun_SimpleFileObject ()
#61 0x0000000000650833 in _PyRun_AnyFileObject ()
#62 0x000000000064f787 in Py_RunMain ()
#63 0x000000000061ee0d in Py_BytesMain ()
#64 0x00007ffff7c6cd90 in __libc_start_call_main (main=main@entry=0x61ed60, argc=argc@entry=2, argv=argv@entry=0x7fffffffdb18) at ../sysdeps/nptl/libc_start_call_main.h:58
#65 0x00007ffff7c6ce40 in __libc_start_main_impl (main=0x61ed60, argc=2, argv=0x7fffffffdb18, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdb08) at ../csu/libc-start.c:392
#66 0x000000000061ec95 in _start ()

对应程序里load_item函数中的self.editor.setPlainText(read_file(fn))这一句调用。这句调用跟构造函数中的self.editor.setPlainText(read_file("segfaults_pyqt5.py"))第一次载入文件完全相同。但第一次是成功的。

问题可能在read_file函数吗?

def read_file(filename) -> str:
    return open(filename, 'r', encoding="utf-8").read()

这个函数经过测试,也没有问题。setPlainText应该也是没有问题的,这么大的bug不可能还会留给我。毕竟大家都有Qt5的源代码。

那么就是两者时间的关系上面。

open & read

  1. 这两个非常基础的函数调用,跟setPlainText会发生什么关联呢?
  2. 程序有三个线程,这与问题有什么关联呢?
  3. 这两个调用有什么不同?

这三个问题直接导向线程安全,thread-safe。我首先怀疑,是python的open和read线程安全问题。

阅读python的文档可以看到,open函数的签名如下:

open(file, mode='r', buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

缓冲参数提到了io.TextIOWrapper

buffering is an optional integer used to set the buffering policy. Pass 0 to switch buffering off (only allowed in binary mode), 1 to select line buffering (only usable in text mode), and an integer > 1 to indicate the size in bytes of a fixed-size chunk buffer. Note that specifying a buffer size this way applies for binary buffered I/O, but TextIOWrapper (i.e., files opened with mode=‘r+’) would have another buffering. To disable buffering in TextIOWrapper, consider using the write_through flag for io.TextIOWrapper.reconfigure(). When no buffering argument is given, the default buffering policy works as follows:
缓冲是一个可选的整数,用于设置缓冲策略。传递0以关闭缓冲(仅允许在二进制模式下),传递1以选择行缓冲(仅在文本模式下可用),传递大于1的整数以指示固定大小的块缓冲区的大小(以字节为单位)。请注意,以这种方式指定缓冲区大小仅适用于二进制缓冲I/O,但是TextIOWrapper(即,使用mode ='r +'打开的文件)将具有另一个缓冲区。要在TextIOWrapper中禁用缓冲,请考虑使用io.TextIOWrapper.reconfigure()的write_through标志。

再跟着追踪到io.TextIOWrapper的文档,可以看到:

TextIOWrapper objects are not thread-safe.

这就是问题的关键所在了。open和read都是线程不安全的。在PyQt5的事件循环中,read的缓冲数据区可能被其他线程修改,导致setPlainText的时候出现问题。

PyQt5的实现(包括setPlainText)应该是线程安全的, 那么我们就把这个部分更换为PyQt5的形式。

QFile

重写read_file函数:

def read_file(filename) -> str:
    fid = QFile(filename)
    if not fid.open(QIODevice.ReadOnly):
        raise IOError
    file_content = str(fid.readAll(), encoding='utf-8')
    fid.close()
    return file_content

这样就可以了。

总结

  1. python的open和read函数不是线程安全的。
  2. PyQt5中应该使用QFile代替open和read函数。
posted @ 2023-05-09 11:58  大福是小强  阅读(25)  评论(0编辑  收藏  举报  来源