WesR
WesR

Reputation: 1512

pyqt5 QTimer.singleShot doesn't run at all

(Python 3.7.3, PyQt5m MacOS Mojave)

In the code that follows I was hoping to see

start_batch
item
item
item
item
returned from start_batch

but what I actually get is

start_batch
returned from start_batch

That is, simulated_item is never running, not even once.

What do I need to do to correct this?

from PyQt5.QtCore import QTimer, QCoreApplication, QThread
import time, sys

class Scanner(object):
    def __init__(self):
        self.num = -1

    def start_batch(self, operator_id, update_callback):
        print('start_batch')
        self.update_callback = update_callback
        QTimer.singleShot(1, self.simulated_item)

    def simulated_item(self):
        print('item')
        self.update_callback()
        self.num += 1
        if self.num > 4:
            self.normal_stop_callback()
            return
        QTimer.singleShot(100, self.simulated_item)

class dummy(QThread):
    def update(self):
        print('update')

    def run(self):
        scnr = Scanner()
        scnr.start_batch('opid', self.update)
        print('returned from start_batch')
        for i in range(10):
            time.sleep((0.2))

app = QCoreApplication([])
thread = dummy()
thread.run()

Upvotes: 1

Views: 2134

Answers (2)

WesR
WesR

Reputation: 1512

As discussed in the comment to @eyllansec above, I was able to create a working example which is shown below.

from PyQt5.QtCore import QTimer, QCoreApplication
import sys

class Scanner(object):
    def __init__(self, app):
        self.num = -1
        self.app = app

    def simulated_item(self):
        self.num += 1
        print("item", self.num)
        if self.num <= 4:
            QTimer.singleShot(500, self.simulated_item)
        else:
            self.app.exit()

if __name__ == "__main__":
    app = QCoreApplication(sys.argv)
    scnr = Scanner(app)
    scnr.simulated_item()
    sys.exit(app.exec_())

Upvotes: 1

eyllanesc
eyllanesc

Reputation: 243897

In your code you have the error that you are calling the run method directly but that is not appropriate but you have to use the start() method:

app = QCoreApplication([])
thread = dummy()
thread.start()
app.exec_()

But you still won't get what you want.

Many elements such as the case of QTimer (also the signals) need an event loop to be able to work but when you override the run method that has it by default you have eliminated it so it fails.

So an approximation of what you want may be the following code where the QTimer uses the QCoreApplication event loop:

from PyQt5.QtCore import QTimer, QCoreApplication, QThread
import time, sys


class Scanner(object):
    def __init__(self):
        self.num = -1

    def start_batch(self, operator_id, update_callback):
        print("start_batch")
        self.update_callback = update_callback
        QTimer.singleShot(1, self.simulated_item)

    def simulated_item(self):
        print("item")
        # QTimer.singleShot() is used so that the update_callback function 
        # is executed in the thread where the QObject to which it belongs lives, 
        # if it is not a QObject it will be executed in the thread 
        # where it is invoked
        QTimer.singleShot(0, self.update_callback)
        # self.update_callback()
        self.num += 1
        if self.num > 4:
            # self.normal_stop_callback()
            return
        QTimer.singleShot(100, self.simulated_item)


class dummy(QThread):
    def update(self):
        print("update")

    def run(self):
        for i in range(10):
            time.sleep(0.2)
        print("returned from start_batch")


if __name__ == "__main__":

    app = QCoreApplication(sys.argv)
    thread = dummy()
    thread.start()
    scnr = Scanner()
    thread.started.connect(lambda: scnr.start_batch("opid", thread.update))
    sys.exit(app.exec_())

Upvotes: 2

Related Questions