[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