Giuseppe
Giuseppe

Reputation: 153

Timer in a QRunnable

I have created the following toy class with help from this answer:

class Worker(QtCore.QThread):
    def work(self):
        print("message")

    def __init__(self):
        super(Worker, self).__init__()
        self.timer = QtCore.QTimer() 
        self.timer.moveToThread(self)
        self.timer.timeout.connect(self.work)

    def run(self):
        self.timer.start(1000)
        loop = QtCore.QEventLoop()
        loop.exec_()

How can I start the timer from a new thread when I use QThreadPool?

I need to update the GUI repeatedly at regular intervals but if I add the QTimer in the main thread the whole application feels really sluggish. My understanding is that by including this in a separate thread through QThreadPool it may be a more efficient solution as the new thread can be self deleted automatically once it is done.

However, whenever I change QtCore.QThread to QtCore.QRunnable in the above class and I try to start the thread using the code below I get an error:

self.threadpool = QtCore.QThreadPool()
worker = Worker()
self.threadpool.start(worker)

Upvotes: 1

Views: 1287

Answers (1)

eyllanesc
eyllanesc

Reputation: 243955

If you want to run a task every T seconds with a QThreadPool then it is not necessary for the QTimer to live in another thread, but for the QTimer to start the QRunnable:

import json
import random
import threading
import time

from PyQt5 import QtCore, QtGui, QtWidgets
import sip


class Signaller(QtCore.QObject):
    dataChanged = QtCore.pyqtSignal(object)


class TimerRunnable(QtCore.QRunnable):
    def __init__(self):
        super().__init__()
        self._signaller = Signaller()

    @property
    def signaller(self):
        return self._signaller

    def run(self):
        print("secondary thread:", threading.current_thread())
        time.sleep(0.5)
        r = random.choice(("hello", (1, 2, 3), {"key": "value"}))
        print("send:", r)
        if not sip.isdeleted(self.signaller):
            self.signaller.dataChanged.emit(r)


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

        self.text_edit = QtWidgets.QTextEdit()
        self.setCentralWidget(self.text_edit)

        timer = QtCore.QTimer(self, timeout=self.on_timeout, interval=1000)
        timer.start()

    @QtCore.pyqtSlot()
    def on_timeout(self):
        runnable = TimerRunnable()
        runnable.signaller.dataChanged.connect(self.on_data_changed)
        QtCore.QThreadPool.globalInstance().start(runnable)

    @QtCore.pyqtSlot(object)
    def on_data_changed(self, data):
        text = json.dumps(data)
        self.text_edit.append(text)


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)

    w = Widget()
    w.show()

    print("main thread:", threading.current_thread())

    sys.exit(app.exec_())

Upvotes: 4

Related Questions