Ben
Ben

Reputation: 795

How to use a timer or equivalent within a python thread without blocking the GUI

I am a writing a GUI based application using the PyQT framework which connects to a device, sends commands, reads the corresponding data and displays this in real time to a table, graph widget and to a file.

Once the run button is clicked it starts a thread which sends the external device commands according to a procedure table and emits signals with the data to various methods to change the GUI.

When the run button is clicked it executes the following lines:

worker = Worker(self.runProcedure)
worker.signals.updateResults.connect(self.updateResultsTable)
worker.signals.writeResults.connect(self.writeResultsFile)
worker.signals.finished.connect(self.procedure_complete) 
     
self.threadpool.start(worker)

within the runProcedure commands are sent to the device from the procedure table and the data read from the device is put into a list 'hfData' using code similar to that listed below:

while float(currentForceReading) <= float(target) and stopAlarm == 0:
    ts = dt.now().timestamp()
    hfData = (connection.readline()).split()
    updateResults_callback.emit(ts, hfData) #method to update results table and graphs
    writeResults_callback.emit(ts, hfData)  #method to write results to a file

One of the options in the application is to hold for a period of time where it continues collecting data from the device without sending new commands.

I am looking for a way to continue taking measurements whilst in this hold time and continue updating the GUI.

I have tried to implement the following code, however this while loop blocks the GUI from updating:

stepHoldTime = float(procedureModel.data(procedureModel.index(row,4), Qt.DisplayRole))
if(stepHoldTime != 0):
    endTime = time.monotonic() + stepHoldTime
    while(time.monotonic() < endTime):
        ts = dt.now().timestamp()
        hfData = (connection.readline()).split()
        updateResults_callback.emit(ts,hfData)

Is there a correct way to implement this functionality?

Upvotes: 1

Views: 783

Answers (2)

PATAPOsha
PATAPOsha

Reputation: 497

You can start your "reader" from a separate thread

class YourUi(QtWidgets.QMainWindow):
    update_data = QtCore.pyqtSignal(str)

    def __init__(self):
        super(YourUi, self).__init__()
        self.update_data.connect(self.do_update_data)

        t = threading.Thread(target=self.update_worker, args=(self.update_data,), daemon=True)
        t.start()

    @staticmethod
    def update_worker(signal):
        connection = create_conncetion()
        while True:
            hfData = (connection.readline()).split()
            signal.emit(hfData)
            time.sleep(0.1)

    def do_update_data(self, hf_data):
        # access GUI elemetns from main thread to prevent freezing
        self.some_qt_label.setText(str(hf_data))

Upvotes: 0

furas
furas

Reputation: 142651

Instead of while-loop you could run QTimer which will execute code every few milliseconds.

It is minimal example which shows how to run function every 1000ms and update time in label.

from PyQt5.QtWidgets import QApplication, QLabel
from PyQt5.QtCore import QTimer, QDateTime

class Window(QLabel):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.showTime()
        
        help(QTimer)
        self.timer = QTimer()
        self.timer.timeout.connect(self.showTime)
        self.timer.start(1000)

    def showTime(self):
        text = QDateTime.currentDateTime().toString('yyyy-MM-dd hh:mm:ss')
        self.setText(text)

if __name__ == '__main__':
    app = QApplication([])
    win = Window()
    win.show()
    app.exec()

But your problem can be more complex and it may need more complex solution. You may have to show minimal working code which we could run and see problem - and test some ideas to resolve problem.

Upvotes: 1

Related Questions