1

I am working on a complex and poorly commented Qt-based Python application. It employs a PySide.QtCore.QTimer.singleShot(int,slot) timer to delay the execution of a slot within a thread, and I am confused about how this timer works.

Here is a MWE. This example uses the approach of subclassing QThread and reimplementing run(). I put the following in a file called timertester.py:

import PySide
import time

class SubClassThread(PySide.QtCore.QThread):

        def run(self):
                print('called SubClassThread.run()')
                self.delayed_print()

        def delayed_print(self):
                print('called SubClassThread.delayed_print()')
                PySide.QtCore.QTimer.singleShot(1000, self.print_things)
                time.sleep(2)
                print('end of delayed_print()')

        def print_things(self):
                print('called print_things')

The code I am using to test this (call it test.py):

import sys
import time

import PySide

from timertester import SubClassThread

print('Test: QThread subclassing')
app = PySide.QtCore.QCoreApplication([])
sct = SubClassThread()
sct.finished.connect(app.exit)
sct.start()
sys.exit(app.exec_())

The output of python test.py:

Test: QThread subclassing
called SubClassThread.run()
called SubClassThread.delayed_print()
end of delayed_print()

The curious part is that the callable passed to QTimer.singleShot never seems to get called (there is no called print_things() in the output!) I would greatly appreciate any clarity that you can shed on this. I feel that I am missing some simple ingredient of the Qt framework. Please bear with me- I did spend some hours searching for answers to this.

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
lensonp
  • 343
  • 3
  • 9

2 Answers2

1

The default implementaion of QThread.run() calls QThread.exec(), which starts the thread's own event-loop. A QTimer requires a running event-loop, and its timeout() signal will be emitted in the thread it is started in. Your implementation of run() does not start an event-loop, so the timer will do nothing.

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • 1
    This reply put me on the right track, so I am giving it the check. I will then add my own answer so people can see the code that ultimately did the trick. – lensonp Aug 10 '16 at 15:41
1

As @ekhumoro pointed out, my reimplementation of run() failed to call exec_(). This was the main problem. It took me a minute to find out where to put the exec_() call and how to properly quit() the thread when its work was finished. Here is the code that works.

Class definition, timertester.py:

import PySide

class SubClassThread(PySide.QtCore.QThread):

        def __init__(self,parent=None):
                print('called SubClassThread.__init__()')
                super(SubClassThread,self).__init__(parent)

        def run(self):
                print('called SubClassThread.run()')
                self.delayed_print()
                self.exec_()

        def delayed_print(self):
                print('called SubClassThread.delayed_print()')
                PySide.QtCore.QTimer.singleShot(1000, self.print_things)
                print('end of delayed_print()')

        def print_things(self):
                print('called print_things')
                self.quit()

Application, test.py:

import sys

import PySide

from timertester import SubClassThread

print('Test: QThread subclassing')

# instantiate a QApplication
app = PySide.QtCore.QCoreApplication([])

# instantiate a thread
sct = SubClassThread()

# when the thread finishes, make sure the app quits too
sct.finished.connect(app.quit)

# start the thread
# it will do nothing until the app's event loop starts
sct.start()

# app.exec_() starts the app's event loop. 
# app will then wait for signals from QObjects.
status=app.exec_()

# print status code and exit gracefully
print('app finished with exit code {}'.format(status))
sys.exit(status)

And, finally, the output of python test.py:

Test: QThread subclassing
called SubClassThread.__init__()
called SubClassThread.run()
called SubClassThread.delayed_print()
end of delayed_print()
called print_things
app finished with exit code 0

What I learned about QTimers: the call to delayed_print() finishes while the timer is still running, and by putting quit() in the method called by the timer, the app does not exit until after that method has been called.

If there are any further comments on this code, or on the aspects of Qt relating to this simple app, please do post! I have had some trouble finding accessible PyQt/PySide information for beginners.

lensonp
  • 343
  • 3
  • 9