[PyQt] Simple polling monitor application

David Hoese dhoese at gmail.com
Fri Jul 19 18:32:31 BST 2013


Yes, that's the magic of Qt4. Once your object is moved to the other 
thread, Qt should run stuff in the correct threads.
I use new-style signals and declare them like this (I think old-style 
are declared in a similar way):

class MyObject(QtCore.QObject):
     new_info = QtCore.pyqtSignal(object) # emitting this signal expects 
one object as an argument
     ... # same as before

# Connect signals as before
my_thread.started.connect(my_obj.run)
my_obj.new_info.connect(gm.on_new_info)

I'm not sure the best way to do the thread id stuff, but the 
documentation talks about it:
http://pyqt.sourceforge.net/Docs/PyQt4/qthread.html#currentThreadId
I'm not sure how reliable that is, given the warnings. Note that it is a 
static method so you can call QtCore.QThread.currentThreadId() from any 
where.

-Dave

On 7/19/13 12:12 PM, Eric Frederich wrote:
> So from that do_stuff method would I be able to emit signals that are
> connected to to slots of objects that would execute in the main GUI
> thread?
> Is there a way to get a thread id or something so I could put print
> statements to verify that things are happening in the correct place?
>
> On Fri, Jul 19, 2013 at 10:37 AM, David Hoese <dhoese at gmail.com> wrote:
>> Ah I forgot one important piece. I structure my QThreads as calling methods
>> of QObjects when they start so I missed that you need to call "self.exec_()"
>> from the QThread's run method (which should be the default in any new-ish
>> versions of PyQt). The exec method starts the Threads own event loop so it
>> can actually process signals (like the Timer) on its own. I'm not sure if
>> it's possible to do this by only using a QThread subclass since the QThread
>> object is just a "handle" to the thread, not the thread itself, see the
>> "You're doing it wrong" post from before.
>>
>> So how I've always done this is create a QObject that has methods for all of
>> the stuff I need to do, create a QThread, move the QObject to that thread,
>> connect up my signals, and start the thread. The blog post I linked to
>> before and the documentation
>> (http://pyqt.sourceforge.net/Docs/PyQt4/qthread.html) should be helpful in
>> understanding this. Here's an example:
>>
>> class MyObject(QtCore.QObject):
>>      def run(self):
>>          # set up timer and start the timer
>>
>>      def do_stuff(self):
>>          # ...do stuff
>>
>> my_thread = QtCore.QThread()
>> my_obj = MyObject()
>> my_obj.moveToThread(my_thread)
>> my_thread.started.connect(my_obj.run)
>> my_thread.start()
>>
>> When I was running older version of PyQt I had to subclass QThread to
>> explicitly call exec_ from the run method:
>>
>> class MyThread(QtCore.QThread):
>>      def run(self):
>>          self.exec_()
>>
>> -Dave
>>
>>
>> On 7/19/2013 9:23 AM, Eric Frederich wrote:
>>> I thought that signals get processed on the same thread that the GUI runs
>>> in.
>>> So in connecting the timer's timeout signal to self.get_stuff, then
>>> wouldn't the get_stuff method run on the same GUI thread?
>>> The point of using another thread was to do the actual long-running
>>> work in the other thread.
>>> I think with what you suggested only starting the timer would be done
>>> in another thread, not the actual work.
>>>
>>> Of course I could be entirely wrong.
>>>
>>> On Fri, Jul 19, 2013 at 9:37 AM, David Hoese <dhoese at gmail.com> wrote:
>>>> Hi Eric,
>>>>
>>>> One thing that was recommended to me when I started doing monitoring GUIs
>>>> was to use a QTimer of 0 to call the polling function. To do this for
>>>> your
>>>> code, move everything in the "while True" into it's own function and make
>>>> it
>>>> run on a per-iteration way. So in your example, "count" would become an
>>>> instance attribute and I think that's it.
>>>>
>>>> Then in the run method you would create a QTimer and set it to call that
>>>> new
>>>> per-iteration function:
>>>>
>>>>       self.my_timer = QtCore.QTimer()
>>>>       self.my_timer.timeout.connect(self.get_stuff)
>>>>       self.my_timer.start(0) # or some amount of delay
>>>>
>>>> The 0 delay makes it continuously add the "get_stuff" method to the event
>>>> loop. This should do what you want. You may run in to trouble when
>>>> closing
>>>> the GUI if your polling does any kind of blocking. Once you tell the
>>>> thread
>>>> to stop (an event in the event loop) the thread won't get to that event
>>>> until the blocking operation is complete. Depending on the length of the
>>>> blocking, your GUI could hang on close for a little bit. There are ways
>>>> around this, but I haven't found one that I love. Hopefully that made
>>>> sense.
>>>>
>>>> Please CC me in any replies, thanks.
>>>>
>>>> -Dave
>>>>
>>>> P.S. I'll point you to a blog post about not subclassing QThread as the
>>>> preferred method (get your stuff working first):
>>>> http://blog.qt.digia.com/blog/2010/06/17/youre-doing-it-wrong/
>>>>
>>>>
>>>> On 7/19/2013 6:00 AM, eric.frederich at gmail.com wrote:
>>>>>
>>>>> Hello,
>>>>>
>>>>> I wrote an example monitoring application where I have a separate
>>>>> thread monitoring something via an external process which could take
>>>>> 20ms to run or it could take 2 seconds to run.
>>>>> Right now I have it just doing a time.sleep(1) for the example.
>>>>>
>>>>> I want this monitoring to continuously happen.
>>>>> It is an endless (while True) loop.
>>>>> I'd prefer to use a QTimer but don't know how to get a QTimer to
>>>>> trigger something in another thread.
>>>>> The work needs to be done in another thread to keep the GUI responsive.
>>>>>
>>>>> Can someone point me in the right direction or modify the following
>>>>> code to use a QTimer rather than an infinite loop with a sleep
>>>>> command?
>>>>>
>>>>>
>>>>> #!/usr/bin/env python
>>>>>
>>>>> from PyQt4.QtCore import *
>>>>> from PyQt4.QtGui import *
>>>>>
>>>>> import time
>>>>> from datetime import datetime
>>>>>
>>>>> class MyThread(QThread):
>>>>>        def __init__(self, parent=None):
>>>>>            super(MyThread, self).__init__(parent)
>>>>>
>>>>>        def run(self):
>>>>>            count = 0
>>>>>            while True:
>>>>>                count += 1
>>>>>                print 'do_something ', count, 'begin'
>>>>>                time.sleep(1)
>>>>>                self.emit(SIGNAL("new_info"), str(count) + ' ' +
>>>>> str(datetime.now()))
>>>>>                print 'do_something ', count, 'end'
>>>>>
>>>>> class MyWidget(QWidget):
>>>>>        def __init__(self, parent=None):
>>>>>            super(MyWidget, self).__init__(parent)
>>>>>            self.setWindowTitle("Monitor")
>>>>>            layout = QVBoxLayout()
>>>>>            self.line = QLineEdit()
>>>>>            layout.addWidget(self.line)
>>>>>            self.setLayout(layout)
>>>>>
>>>>>            self.mon = MyThread()
>>>>>            self.connect(self.mon, SIGNAL("new_info"), self.on_new_info)
>>>>>            self.mon.start()
>>>>>
>>>>>        def on_new_info(self, info):
>>>>>            print 'got new info', info
>>>>>            self.line.setText(info)
>>>>>
>>>>> if __name__ == '__main__':
>>>>>        import sys
>>>>>        app = QApplication(sys.argv)
>>>>>        gm = MyWidget()
>>>>>        gm.show()
>>>>>        sys.exit(app.exec_())



More information about the PyQt mailing list