TheNarogly
TheNarogly

Reputation: 3

Running a heavy QTimer task as a QThread

I have a heavy task that constantly runs every 500ms. It consists of updating GUI elements and I need access to its variables at all times.

The task performed: A list that is dynamically updated and every 500ms, a loop goes through that list and performers tasks on the elements contained inside of it. Sometimes I have no elements in it, and sometimes I have plenty.

When the list is loaded, the user starts to encounter a delay in mouse movement, key presses, and such. And that's without a doubt due to the heavy task performed every 500ms.

Would there be a way for me to put this QTimer task into a QThread and constantly have access to it's elements in order update the list contained inside of it?

In other words, I would like it to run in the background at all times but also have the ability to update the list used inside of it at any given moment.

I'm using PySide2; I've seen examples but none that fit what I'm trying to accomplish.

EXAMPLE: I would like to update the "aList" element from the main thread as I wish. If the list is empty, then the for loop does not do anything. Otherwise, it loops over the elements and adds 1 to them.

The "run" function should have a Qtimer of 500ms set on it.

Sometimes the list may be empty and at times full of elements. It's size is controlled from the GUI thread.

 from PySide2 import QtCore
 from PySide2 import QtGui 
 from PySide2 import QtWidgets

 import sys
 import time

 class RxProcess(QtCore.QThread):

     output = QtCore.Signal()

     def __init__(self, parent = None):
         super(RxProcess, self).__init__(parent)
         self.aList = list()

    
     def run(self):
    
         # Loop through list
         for element in self.aList: 
        
             element += 1

             # Print to the gui the element that was just updated in the list
             self.output.emit(element)

Upvotes: 0

Views: 242

Answers (1)

eyllanesc
eyllanesc

Reputation: 243907

With QThread it is difficult to implement that logic (you would have to use QThread.msleep, mutex, etc). Instead a simple solution is to create a new thread every T seconds and that will be implemented using threading.Thread + QTimer (can also be implemented with QThreadPool + QRunnable + QTimer):

import random
import sys
import threading
import time


from PySide2 import QtCore, QtWidgets
import shiboken2


class Worker(QtCore.QObject):
    output = QtCore.Signal(object)


def long_running_function(values, worker):
    for element in values:
        time.sleep(0.1)
        if shiboken2.isValid(worker):
            worker.output.emit(element)


class Widget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.label = QtWidgets.QLabel(alignment=QtCore.Qt.AlignCenter)
        self.button = QtWidgets.QPushButton("Start")

        lay = QtWidgets.QVBoxLayout(self)
        lay.addWidget(self.button)
        lay.addWidget(self.label)

        self.timer = QtCore.QTimer(interval=500)

        self.button.clicked.connect(self.handle_clicked)
        self.timer.timeout.connect(self.launch_task)

    def handle_clicked(self):
        if self.button.text() == "Start":
            self.timer.start()
            self.button.setText("Stop")
        elif self.button.text() == "Stop":
            self.timer.stop()
            self.button.setText("Start")

    def launch_task(self):
        values = random.sample(range(1, 50), 20)
        worker = Worker()
        worker.output.connect(self.label.setNum)
        threading.Thread(
            target=long_running_function,
            args=(values, worker),
            daemon=True,
        ).start()


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    w = Widget()
    w.show()
    sys.exit(app.exec_())

Upvotes: 1

Related Questions