[Python] PyQt5 GUI freezes after hours while background processing continues (no exceptions...

Discussão em 'Python' iniciado por Stack, Junho 18, 2025.

  1. Stack

    Stack Membro Participativo

    I'm developing a PyQt5-based desktop application that listens to data from a Kepware OPC server and displays the most recent image (from a camera or screenshot) on a QLabel using QPixmap. There is no user interaction involved (no buttons, clicks, etc.). The application also logs data to a database in the background.

    The GUI works fine initially and updates regularly. However, after some time — sometimes 1 hour, sometimes 5-6 hours — the GUI freezes: the QLabel stops updating, and the last image remains stuck on screen. The backend continues to work, database logging and computations proceed normally. There are no exceptions thrown, and all methods are wrapped in try-except blocks.

    Here's what I’ve tried so far:

    Ensured my EXE (built with PyInstaller) has the necessary Kepware licenses

    Suspected the issue might be with updating the QLabel with QPixmap

    Considered a memory leak or slow GUI thread death, but have no solid evidence

    Verified no infinite loops or blocking code on the main thread

    I'm looking for help debugging this GUI freeze — why would QLabel.setPixmap() silently stop updating, even though the rest of the program keeps running? What else should I check?

    import os
    import asyncio
    import datetime
    from qasync import asyncSlot
    from PyQt5 import uic
    from PyQt5.QtCore import Qt, QTimer
    from PyQt5.QtGui import QPixmap, QImage
    from PyQt5.QtWidgets import QMainWindow, QLabel
    from src.app_data import AppData
    from src.kepware_helper import KepwareHelper


    class MainScreen(QMainWindow):
    def __init__(self):
    try:
    super(MainScreen, self).__init__()
    current_dir = os.getcwd()

    print("current dir", current_dir)
    ui_file_path = os.path.join(current_dir, "assets", "gui.ui")
    uic.loadUi(ui_file_path, self)
    self.app_data = AppData()

    self.pixmap = QPixmap(os.path.join(current_dir, "assets", "initpixmap.png"))
    self.initialize_ui_elements()
    QTimer.singleShot(0, self.initialize_thread_definitions)
    except Exception as e:
    print("Error initializing MainScreen:", str(e))
    self.app_data.get_log_helper().append_log(f"Error initializing MainScreen: {str(e)}")

    '''
    Handles the close event of the main window.
    This method unsubscribes from Kepware and disconnects before closing the application.
    :param event: The close event
    '''
    def closeEvent(self, event):
    try:
    self.update_status('Closing the application...')
    if self.kepware_helper:
    self.kepware_helper.unsubscribe()
    self.kepware_helper.disconnect_kepware()
    os._exit(0)
    return super().closeEvent(event)
    except Exception as e:
    print("Error closing MainScreen:", str(e))
    self.app_data.get_log_helper().append_log(f"Error closing MainScreen: {str(e)}")

    '''
    Initializes the UI elements by finding them in the UI file.
    This method sets up the labels and pixmap for the main screen.
    It also handles any exceptions that may occur during the initialization process.
    '''
    def initialize_ui_elements(self):
    try:
    self.modelLabel = self.findChild(QLabel, "modelLabel")
    self.dummyLabel = self.findChild(QLabel, "dummyLabel")
    self.dateLabel = self.findChild(QLabel, "dateLabel")
    self.statusLabel = self.findChild(QLabel, "statusLabel")
    self.pixmapLabel = self.findChild(QLabel, "pixmapLabel")
    self.pixmapLabel.setPixmap(self.pixmap)
    self.pixmapLabel.setScaledContents(True)
    except Exception as e:
    print("Error initializing UI elements:", str(e))
    self.app_data.get_log_helper().append_log(f"Error initializing UI elements: {str(e)}")

    '''
    Initializes the thread definitions and connects to Kepware.
    This method sets up the KepwareHelper instance, connects to Kepware, initializes nodes, and subscribes to updates.
    It also handles any exceptions that may occur during the initialization process.
    '''
    @asyncSlot()
    async def initialize_thread_definitions(self):
    try:
    self.kepware_helper = KepwareHelper(
    self.update_status,
    self.update_pixmap,
    self.result_handler,
    self.update_model_label,
    self.update_dummy_label,
    )
    await self.kepware_helper.connect_kepware()
    await self.kepware_helper.initialize_nodes()
    await self.kepware_helper.subscribe()
    except Exception as e:
    print("Error initializing threads:", str(e))
    self.app_data.get_log_helper().append_log(f"Error initializing threads: {str(e)}")

    '''
    self.pixmap degerini gelen qImage ile doldurur
    eger qImage bos gelirse transparent doldurur.
    '''
    def update_pixmap(self, qImage = None):
    try:
    # Get the size of the settings image label
    if qImage:
    self.settingImageWidth = qImage.rect().width()
    self.settingImageHeight = qImage.rect().height()
    self.pixmap = QPixmap.fromImage(qImage)
    self.settingLastImage = qImage
    else:
    self.pixmap = QPixmap()
    self.pixmap.fill(Qt.transparent)

    original_pixmap = self.pixmap
    original_width = original_pixmap.width()
    original_height = original_pixmap.height()

    label_width = self.pixmapLabel.width()
    label_height = self.pixmapLabel.height()

    # En-boy oranını koruyarak yeni boyutları hesapla
    if original_width == 0 or original_height == 0 or label_width == 0 or label_height == 0:
    # Boyutlardan biri sıfırsa pixmap'i doğrudan temizle
    self.pixmapLabel.clear()
    return

    aspect_ratio = original_width / original_height
    if label_width / label_height > aspect_ratio:
    new_width = int(label_height * aspect_ratio)
    new_height = label_height
    else:
    new_width = label_width
    new_height = int(label_width / aspect_ratio)

    scaled_pixmap = original_pixmap.scaled(new_width, new_height, Qt.KeepAspectRatio)

    # Yeniden boyutlandırılmış resmi etikete yerleştir
    self.pixmapLabel.setPixmap(scaled_pixmap)
    self.pixmapLabel.setScaledContents(True)
    except Exception as e:
    print("Error updating pixmap:", str(e))
    self.app_data.get_log_helper().append_log(f"Error updating pixmap: {str(e)}")

    '''
    thread tarafinda hesaplama yapilan islemlerin
    durumunu ekranda gostermek icin kullanilir.
    '''
    def update_status(self, message):
    try:
    self.statusLabel.setText(message)
    except Exception as e:
    print("Error updating status:", str(e))
    self.app_data.get_log_helper().append_log(f"Error updating status: {str(e)}")


    '''
    Handles the result of a test.
    :param result: Boolean indicating the success of the test
    :param dummyNo: Dummy number associated with the test
    :param productNo: Product number associated with the test
    '''
    def result_handler(self, result):
    try:
    if result:
    self.statusLabel.setText("Sonuç: Başarılı")
    else:
    self.statusLabel.setText("Sonuç: Başarısız")
    self.dateLabel.setText(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
    except Exception as e:
    print("Error handling result:", str(e))
    self.app_data.get_log_helper().append_log(f"Error handling result: {str(e)}")

    '''
    Updates the model label with the given model.
    :param model: Model to be displayed
    '''
    def update_model_label(self, model):
    try:
    if model and model != "":
    self.modelLabel.setText(str(model))
    else:
    self.modelLabel.setText("Ürün Bekleniyor....")
    except Exception as e:
    print("Error updating model label:", str(e))
    self.app_data.get_log_helper().append_log(f"Error updating model label: {str(e)}")

    '''
    Updates the dummy label with the given dummy number.
    :param dummy_no: Dummy number to be displayed
    '''
    def update_dummy_label(self, dummy_no):
    try:
    if dummy_no and dummy_no != "":
    self.dummyLabel.setText(str(dummy_no))
    else:
    self.dummyLabel.setText("Ürün Bekleniyor....")
    except Exception as e:
    print("Error updating dummy label:", str(e))
    self.app_data.get_log_helper().append_log(f"Error updating dummy label: {str(e)}")

    '''
    Converts an OpenCV image to a QImage.
    :param image: OpenCV image (numpy array)
    :return: QImage object
    '''
    def convert_to_qImage(self, image):
    try:
    height, width, channel = image.shape
    bytesPerLine = 3 * width
    qImg = QImage(image.data, width, height, bytesPerLine, QImage.Format_RGB888)

    return qImg
    except Exception as e:
    print("Error converting to QImage:", str(e))
    self.app_data.get_log_helper().append_log(f"Error converting to QImage: {str(e)}")
    return None

    Continue reading...

Compartilhe esta Página