Python crashes when running a log streamer using Qt

百般思念 提交于 2021-02-05 06:23:04

问题


Goal

I have a process that logs on a file (realtime.log) while running and I want to print every new line of that file in my application in realtime. In other words I want to redirect the output from the process to the GUI. This means that I have two different processes running: the "engine" and the GUI.

I have already achieved this by using Tkinter but since I have to make a more complex, professional and good looking GUI I decided to switch to Qt for Python (PySide2).

Problem

Python often crashes when I launch the GUI with the error message: Python has stopped working. The window starts printing the lines and at some point it stops working.

After many attempts and searches I got to a point where the program only crashes if I click on the GUI window. Moreover, the program doesn't crash suddenly but it crashes at the end of the engine's execution.

Environment

  • Windows 10
  • Python 3.6.5
  • PySide2 5.12.6

Code

Note that this is a simplified version.

datalog_path = "realtime.log"


def get_array_from_file(file_path):
    try:
        with open(file_path, 'r') as file:
            lines = file.readlines()
        return lines
    except:
        print('error in file access')


class Streamer(QRunnable):
    def __init__(self, stream, old_array, edit):
        super().__init__()
        self.stream = stream
        self.old_array = old_array
        self.edit = edit

    def run(self):
        try:
            while self.stream:
                array_file = get_array_from_file(datalog_path)
                if len(array_file) != len(self.old_array):
                    for line in array_file[len(self.old_array) - 1:len(array_file)]:
                        self.edit.append(line)
                        # print(line)
                        self.old_array.append(line)
        except:
            print('problem in streaming funct')


class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        layout = QVBoxLayout()

        self.setWindowTitle("DATALOG")
        self.thread_pool = QThreadPool()
        self.edit = QTextEdit()

        self.stream = True
        self.old_array = get_array_from_file(datalog_path)
        self.streamer = Streamer(self.stream, self.old_array, self.edit)
        self.thread_pool.start(self.streamer)

        window = QWidget()
        layout.addWidget(self.edit)
        window.setLayout(layout)
        self.setCentralWidget(window)


    def closeEvent(self, event):
        self.stream = False
        event.accept()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = Window()
    win.show()
    app.exec_()

回答1:


The @hyde answer points out explains the reason for the problem but its solution is not applicable in PySide2 (in PyQt5 a small modification would have to be made, see this), an alternative is to create a QObject that has the signals:

class Signaller(QtCore.QObject):
    textChanged = Signal(str)
class Streamer(QRunnable):
    def __init__(self, stream, old_array):
        super().__init__()
        self.stream = stream
        self.old_array = old_array
        self.signaller = Signaller()

    def run(self):
        try:
            while self.stream:
                array_file = get_array_from_file(datalog_path)
                if len(array_file) != len(self.old_array):
                    for line in array_file[len(self.old_array) - 1:len(array_file)]:
                        self.signaller.textChanged.emit(line)
                        # print(line)
                        self.old_array.append(line)
        except:
            print('problem in streaming funct')
self.stream = True
self.old_array = get_array_from_file(datalog_path)
self.streamer = Streamer(self.stream, self.old_array)
self.streamer.signaller.textChanged.connect(self.edit.append)
self.thread_pool.start(self.streamer)



回答2:


While I'm not too familiar with Python Qt, issue is probably, that you use a GUI object edit from a different thread. This is not allowed, the GUI part must all run in the same (main) thread!

To fix this, you need to have some other way for the thread to communicate UI changes. Since your QRunnable is not a QObject, you can't just emit a signal, but you can use QMetaObject::invokeMethod on it's invokable methods. Please let me know if this works directly:

# self.edit.append(line) # can't do this from a thread!
# instead, invoke append through GUI thread event loop
QtCore.QMetaObject.invokeMethod(self.edit, 
                                'append', 
                                QtCore.Qt.QueuedConnection, 
                                QtCore.QGenericArgument('QString', line)


来源:https://stackoverflow.com/questions/62875067/python-crashes-when-running-a-log-streamer-using-qt

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!