Python вылетает при запуске стримера журналов с использованием Qt - PullRequest
3 голосов
/ 13 июля 2020

Цель

У меня есть процесс, который во время работы регистрирует файл (realtime.log), и я хочу печатать каждую новую строку этого файла в моем приложении в реальном времени. Другими словами, я хочу перенаправить вывод процесса на GUI. Это означает, что у меня работают два разных процесса : «движок» и GUI.

Я уже добился этого с помощью Tkinter, но, поскольку мне нужно сделать более сложный, профессиональный и красивый GUI Я решил перейти на Qt для Python ( PySide2 ).

Проблема

Python часто вылетает, когда я запускаю GUI с сообщением об ошибке: Python перестало работать . Окно начинает печатать строки и в какой-то момент перестает работать.

После многих попыток и поисков я дошел до точки, когда программа вылетает только при нажатии на окно GUI. Более того, программа не обнаруживает sh внезапно, а дает сбой в конце работы движка.

Environment

  • Windows 10
  • Python 3.6.5
  • PySide2 5.12.6

Код

Обратите внимание, что это упрощенная версия.

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

Ответы [ 2 ]

3 голосов
/ 13 июля 2020

Ответ @ hyde указывает, объясняет причину проблемы, но его решение не применимо в PySide2 (в PyQt5 необходимо внести небольшую модификацию, см. this ) альтернативой является создание QObject с сигналами:

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
        <b>self.signaller = Signaller()</b>

    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)]:
                        <b>self.signaller.textChanged.emit(line)</b>
                        # 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)
<b>self.streamer = Streamer(self.stream, self.old_array)
self.streamer.signaller.textChanged.connect(self.edit.append)</b>
self.thread_pool.start(self.streamer)
1 голос
/ 13 июля 2020

Хотя я не слишком знаком с Python Qt, проблема, вероятно, в том, что вы используете GUI объект edit из другого потока. Это недопустимо, часть GUI должна работать в одном (основном) потоке!

Чтобы исправить это, вам нужно иметь какой-то другой способ для потока передавать изменения пользовательского интерфейса. Поскольку ваш QRunnable не является QObject , вы не можете просто испустить сигнал, но вы можете использовать QMetaObject::invokeMethod в его вызываемых методах. Пожалуйста, дайте мне знать, работает ли это напрямую:

# 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)
...