PyQt5: Timer in a thread
Asked Answered
B

1

8

Problem Description

I'm trying to make an application that collects data, processes it, displays it, and some actuation (open/close valves, etc). As a practice for future applications where I have some stricter time constraints, I want to run the data capture and processing in a separate thread.

My current problem is that it's telling me I cannot start a timer from another thread.

Current code progress

import sys
import PyQt5
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QThread, pyqtSignal

# This is our window from QtCreator
import mainwindow_auto

#thread to capture the process data
class DataCaptureThread(QThread):
    def collectProcessData():
        print ("Collecting Process Data")
    #declaring the timer
    dataCollectionTimer = PyQt5.QtCore.QTimer()
    dataCollectionTimer.timeout.connect(collectProcessData)
    def __init__(self):
        QThread.__init__(self)

    def run(self):
        self.dataCollectionTimer.start(1000);

class MainWindow(QMainWindow, mainwindow_auto.Ui_MainWindow):
    def __init__(self):
        super(self.__class__, self).__init__()
        self.setupUi(self) # gets defined in the UI file
        self.btnStart.clicked.connect(self.pressedStartBtn)
        self.btnStop.clicked.connect(self.pressedStopBtn)

    def pressedStartBtn(self):
        self.lblAction.setText("STARTED")
        self.dataCollectionThread = DataCaptureThread()
        self.dataCollectionThread.start()
    def pressedStopBtn(self):
        self.lblAction.setText("STOPPED")
        self.dataCollectionThread.terminate()


def main():
     # a new app instance
     app = QApplication(sys.argv)
     form = MainWindow()
     form.show()
     sys.exit(app.exec_())

if __name__ == "__main__":
     main()

Any advice on how to get this to work would be appreciated!

Backwash answered 16/11, 2017 at 21:5 Comment(0)
T
10

You have to move the QTimer to the DataCaptureThread thread, in addition to that when the run method ends, the thread is eliminated so the timer is eliminated, so you must avoid running that function without blocking other tasks. QEventLoop is used for this:

class DataCaptureThread(QThread):
    def collectProcessData(self):
        print ("Collecting Process Data")

    def __init__(self, *args, **kwargs):
        QThread.__init__(self, *args, **kwargs)
        self.dataCollectionTimer = QTimer()
        self.dataCollectionTimer.moveToThread(self)
        self.dataCollectionTimer.timeout.connect(self.collectProcessData)

    def run(self):
        self.dataCollectionTimer.start(1000)
        loop = QEventLoop()
        loop.exec_()
Tomkins answered 16/11, 2017 at 21:30 Comment(10)
Thank you! this worked after editing the line: self.dataCollectionTimer.timeout.connect(self.collectProcessData) to self.dataCollectionTimer.timeout.connect(lambda:self.collectProcessData) Otherwise it would give the error: TypeError: collectProcessData() takes 0 positional arguments but 1 was given Which I don't really understand since no arguments were passedBackwash
@Backwash If it worked for you, do not forget to mark my answer as correct.Tomkins
@ellyanesc is it okay if I add in the lambda to your answer?Backwash
Change def collectProcessData() to def collectProcessData(self)Tomkins
Correction: The code runs, but "Collecting Process Data" never gets printed to the console. I added a print ("Called Start") right after starting the timer in run, and it gets printed @ellyanescBackwash
@Backwash Maybe I forgot to put some information, use the following example as a test and it works correctly: gist.github.com/eyllanesc/1f7cf4b05db060286f60487baa0e6146Tomkins
@ellyanesc Thanks a million! The difference that made it work was in the init between what you and I had. I only had self in the arguments, and you had self, *args, **kwargs I don't really know why this would cause the problem, but your example solved it. Thank youBackwash
@Backwash usually I test before posting an answer so if the next time I help you and there is a problem ask me for my test code :PTomkins
I'll make sure I do! If you have an idea on why my init caused that without giving me some error or warning please let me knowBackwash
collectProcessData() does not run in a seperate thread. Put a time.sleep(10) inside that function and you will see it will freeze up your main GUI.Desberg

© 2022 - 2024 — McMap. All rights reserved.