1

I was going through QThread() document where it is mentioned to create a new class worker and then create QThread in main function and afterwards use movingToThread

https://realpython.com/python-pyqt-qthread/#:~:text=In%20PyQt%2C%20you%20use%20QThread,the%20thread%20as%20an%20argument.

My question is I have a QMainWindow, inside that function called update_table_live which is basically updating my table widget cell value in while loop. I want this function to be a part of QThread? If I go with example of realpython then it is making me confused as my QMainWindow contains all object as self So how should I make them separate in worker class just like shown in realpython website or is there any other way to create QThread for function inside mainWindow?

Trying to make a clean version of code from original.

import utility
from PyQt5.QtWidgets import *  
from PyQt5.QtCore import Qt, QThread, QObject, pyqtSignal
import sys

class mainUI(QWidget):
    def __init__(self, parent=None):
        """ This module setup entire widgets for UI
        with setup of some basic functionality.

        Args:
            parent: None
        """
        QWidget.__init__(self, parent, Qt.WindowStaysOnTopHint)

        self.setMinimumSize(1000, 500)

        self.table_live = QTableWidget()
        self.table_live.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.table_live.setColumnCount(6)
        self.table_live.setHorizontalHeaderLabels(
        ['Name', 'Quantity', 'Discount Price', 'Market Price', 'Change', 'P/L']
        )

        self.layout = QHBoxLayout()
        self.layout.addWidget(self.table_live)
        self.setLayout(self.layout)
        
        #: This Function to be in thread
        self.update_table_live()

    def update_table_live(self):
        """ This method will update live table
       on current market value.

        Returns:
            None

        """
        while True:
            market = utility.get_live_data()  #: This function get live data.
            self.table_live.setRowCount(0)
    
            for name, product_data in market.items():
                row_count = self.table_live.rowCount()
                self.table_live.insertRow(row_count)
    
                item_name = QTableWidgetItem(name)
                item_quantity = QTableWidgetItem(product_data['quantity'])
                item_discount = QTableWidgetItem(product_data['discount'])
                item_current = QTableWidgetItem(product_data['price'])
                item_change = QTableWidgetItem(product_data['change'])
    
                item_pl = QTableWidgetItem(product_data['p_l'])
    
                self.table_live.setItem(row_count, 0, item_name)
                self.table_live.setItem(row_count, 1, item_quantity)
                self.table_live.setItem(row_count, 2, item_discount)
                self.table_live.setItem(row_count, 3, item_current)
                self.table_live.setItem(row_count, 4, item_change)
                self.table_live.setItem(row_count, 5, item_pl)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = mainUI()
    win.show()
    app.exec_()
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
mehulJ
  • 109
  • 1
  • 12

1 Answers1

3

The main problem in your code is the while True since this in itself blocks the eventloop so you have 2 options:

Threads

NOTE: If the get_live_data() task is time consuming then you should use threads.

You have to be clear about the following:

  • Threads should only be used as a last option.
  • Threads should only execute tasks that consume a lot of time since if they are executed in the main thread, the Qt eventloop will be blocked and consequently the GUI will freeze.
  • The GUI should not be modified directly in the threads, instead you should send the information through signals so that it is updated in the main thread.

Considering the above, the solution is:

class Worker(QObject):
    dataChanged = pyqtSignal(dict)

    def task(self):
        while True:
            market = utility.get_live_data()
            self.dataChanged.emit(market)


class mainUI(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent, Qt.WindowStaysOnTopHint)

        self.setMinimumSize(1000, 500)

        self.table_live = QTableWidget()
        self.table_live.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.table_live.setColumnCount(6)
        self.table_live.setHorizontalHeaderLabels(
            ["Name", "Quantity", "Discount Price", "Market Price", "Change", "P/L"]
        )

        layout = QHBoxLayout(self)
        layout.addWidget(self.table_live)

        self.worker = Worker()
        thread = QThread(self)
        self.worker.moveToThread(thread)
        self.worker.dataChanged.connect(self.update_table_live)
        thread.started.connect(self.worker.task)
        thread.start()

    def update_table_live(self, market):
        for name, product_data in market.items():
            row_count = self.table_live.rowCount()
            self.table_live.insertRow(row_count)

            item_name = QTableWidgetItem(name)
            item_quantity = QTableWidgetItem(product_data["quantity"])
            item_discount = QTableWidgetItem(product_data["discount"])
            item_current = QTableWidgetItem(product_data["price"])
            item_change = QTableWidgetItem(product_data["change"])

            item_pl = QTableWidgetItem(product_data["p_l"])

            self.table_live.setItem(row_count, 0, item_name)
            self.table_live.setItem(row_count, 1, item_quantity)
            self.table_live.setItem(row_count, 2, item_discount)
            self.table_live.setItem(row_count, 3, item_current)
            self.table_live.setItem(row_count, 4, item_change)
            self.table_live.setItem(row_count, 5, item_pl)

QTimer

NOTE: If get_live_data is not very time consuming then it is better to use QTimer to avoid blocking loop.

class mainUI(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent, Qt.WindowStaysOnTopHint)

        self.setMinimumSize(1000, 500)

        self.table_live = QTableWidget()
        self.table_live.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.table_live.setColumnCount(6)
        self.table_live.setHorizontalHeaderLabels(
            ["Name", "Quantity", "Discount Price", "Market Price", "Change", "P/L"]
        )

        layout = QHBoxLayout(self)
        layout.addWidget(self.table_live)

        self.timer = QTimer(interval=100, timeout=self.handle_timeout)
        self.timer.start()

    def handle_timeout(self):
        market = utility.get_live_data()
        self.update_table_live(market)

    def update_table_live(self, market):
        for name, product_data in market.items():
            row_count = self.table_live.rowCount()
            self.table_live.insertRow(row_count)

            item_name = QTableWidgetItem(name)
            item_quantity = QTableWidgetItem(product_data["quantity"])
            item_discount = QTableWidgetItem(product_data["discount"])
            item_current = QTableWidgetItem(product_data["price"])
            item_change = QTableWidgetItem(product_data["change"])

            item_pl = QTableWidgetItem(product_data["p_l"])

            self.table_live.setItem(row_count, 0, item_name)
            self.table_live.setItem(row_count, 1, item_quantity)
            self.table_live.setItem(row_count, 2, item_discount)
            self.table_live.setItem(row_count, 3, item_current)
            self.table_live.setItem(row_count, 4, item_change)
            self.table_live.setItem(row_count, 5, item_pl)
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • I did try with QTimer but it do freezes GUI, also other part of UI is not operational if QTimer is running. Sorry I didn't mentioned earlier about other GUI widgets. – mehulJ Jun 01 '21 at 13:36
  • In this case `Threads` is helpful. Steps recommended by you is working great but I have two question on top of it 1 - I want to limit QThread to use only one CPU thread? 2 - I want to control thread stop process with pushbutton I don't know how to stop thread I tried `button_stop.clicked.connect(thread.terminate)` or `button_stop.clicked.connect(thread.quit)` but nothing happening. – mehulJ Jun 01 '21 at 13:37
  • @mehulJ 1) Qt only uses CPU Threads (or in general we cannot control it), 2) There are many examples in SO of how to stop the execution of a thread so I recommend you to do a search. – eyllanesc Jun 01 '21 at 15:22
  • I did search tens of article where they have suggested to use ‘exit()’ or ‘terminate()’ but in my case both are not working I did put a condition in between to check whether state ‘isRunning()’ and every time it returns ‘True’ – mehulJ Jun 01 '21 at 15:25
  • @mehulJ maybe https://stackoverflow.com/questions/16246796/how-to-stop-a-qthread-from-the-gui – eyllanesc Jun 01 '21 at 15:26