How to turn non-thread safe pyqt program into thread safe program?
-
wrote on 10 Aug 2022, 18:19 last edited by
I have a PyQt GUI application that will just randomly crash and will not produce an error message at all. The crashes are not consistent. Sometimes it happens immediately and then other times it won't happen for a day or two. I have looked online and I believe that it is a threading issue but I haven't been able to trace down where. I've included an example below. My original program is too large to paste here but the threading I am doing in the smaller example is the exact same way I am handling threading in my larger application.
I am aware that widgets are not thread safe and that I cannot directly access them from external threads and that I need to use signals but I do not know how to do that.
I believe that below the issue would be appending to the text box while the process is running, so how do I instead use a signal to do this safely?
import logging import os import time from pathlib import Path import sys from PyQt5.QtCore import QObject, QThread, pyqtSignal from PySide2.QtWidgets import QApplication, QWidget from PySide2.QtCore import QFile, QObject from PySide2.QtUiTools import QUiLoader logger_real_time = logging.getLogger(__name__) class ConsoleWindowLogHandler(logging.Handler, QObject): sigLog = pyqtSignal(str) def __init__(self): logging.Handler.__init__(self) QObject.__init__(self) def emit(self, log_record): message = str(log_record.getMessage()) self.sigLog.emit(message) class Worker(QThread): def __init__(self, func, args): super(Worker, self).__init__() self.func = func self.args = args def run(self): self.func(*self.args) class Widget(QWidget): def __init__(self): super(Widget, self).__init__() self.ui = self.load_ui() self.bee = Worker(self.start_a_process, ()) self.run_main_gui() def load_ui(self): loader = QUiLoader() current_path = os.fspath(Path(__file__).resolve().parent / "form.ui") ui_file = QFile(current_path) if ui_file.open(QFile.ReadOnly): self.ui = loader.load(ui_file, self) ui_file.close() return self.ui def start_a_process(self): print('sleeping') for i in range(0, 10): self.ui.a_text_edit.append("Entering function") time.sleep(1) self.ui.a_text_edit.append("Exiting function") def run_main_gui(self): self.ui.start_pb.clicked.connect(self.bee.start) if __name__ == "__main__": app = QApplication([]) widget = Widget() widget.show() sys._excepthook = sys.excepthook def exception_hook(exctype, value, traceback_val): print(exctype, value, traceback_val) sys._excepthook(exctype, value, traceback_val) sys.exit(1) sys.excepthook = exception_hook sys.exit(app.exec_())
# -*- coding: utf-8 -*- ################################################################################ ## Form generated from reading UI file 'form.ui' ## ## Created by: Qt User Interface Compiler version 5.15.2 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ from PySide2.QtCore import * from PySide2.QtGui import * from PySide2.QtWidgets import * class Ui_Widget(object): def setupUi(self, Widget): if not Widget.objectName(): Widget.setObjectName(u"Widget") Widget.resize(310, 184) self.start_pb = QPushButton(Widget) self.start_pb.setObjectName(u"start_pb") self.start_pb.setGeometry(QRect(100, 40, 93, 28)) self.a_text_edit = QTextEdit(Widget) self.a_text_edit.setObjectName(u"a_text_edit") self.a_text_edit.setGeometry(QRect(10, 80, 291, 87)) self.retranslateUi(Widget) QMetaObject.connectSlotsByName(Widget) # setupUi def retranslateUi(self, Widget): Widget.setWindowTitle(QCoreApplication.translate("Widget", u"Widget", None)) self.start_pb.setText(QCoreApplication.translate("Widget", u"Start", None)) # retranslateUi
-
Hi,
You have here a pretty nice article about that matter.
It shows how to use the worker object paradigm to update the GUI from a long running task.
-
Hi,
You have here a pretty nice article about that matter.
It shows how to use the worker object paradigm to update the GUI from a long running task.
wrote on 10 Aug 2022, 18:58 last edited by@SGaist I'll look into it. Thank you
-
Hi,
You have here a pretty nice article about that matter.
It shows how to use the worker object paradigm to update the GUI from a long running task.
wrote on 11 Aug 2022, 15:57 last edited by freemanl144 8 Nov 2022, 16:02@SGaist Okay so I looked at what you sent yesterday and have come up with this so far
class WorkerThread(QThread): update_progress = pyqtSignal(str) def get_message(self, message): self.message = message def run(self): self.update_progress.emit(self.message)
In main class
def write_to_log_box(self, message): try: self.worker = WorkerThread() self.worker.get_message(message) self.worker.start() self.worker.update_progress.connect(self.evt_update_progress) except (Exception, ): print("Failure") def evt_update_progress(self, message): self.ui.tb_log_window.append(message)
This will output one log message but then it crashes. Am I on the right track here? Do I have to call start every single time that I want to send a message to the log box?
This is being triggered by every time I want to log a message to my message box I am calling write_to_log_box and passing in a message
-
Can you explain more precisely the type of workload you want to implement that is threaded ?
Your minimal object here does make much sense in terms of exploiting threads as it will only run once so it's an overkill.
In any case, you should setup all connections before starting a thread.
-
Can you explain more precisely the type of workload you want to implement that is threaded ?
Your minimal object here does make much sense in terms of exploiting threads as it will only run once so it's an overkill.
In any case, you should setup all connections before starting a thread.
wrote on 11 Aug 2022, 17:31 last edited by@SGaist All I am trying to do is set up a log window. The application will run through a set of measurements and then report those values back to the log window, about 10 every second
I am attempting to turn the smaller threading example that I currently have into a larger one that I am using in my main application.
-
@SGaist All I am trying to do is set up a log window. The application will run through a set of measurements and then report those values back to the log window, about 10 every second
I am attempting to turn the smaller threading example that I currently have into a larger one that I am using in my main application.
wrote on 11 Aug 2022, 18:50 last edited by@freemanl144
Normally you have the (one) worker thread continually running away busy doing computations. From time to time it emits a progress message. The main UI thread has a slot on that signal and puts the passed message into a visual widget.
1/7