PyQt4: Interrupt QThread exec when GUI is closed - python

I have a PyQt4 GUI that has three threads. One thread is a data source, it provides numpy arrays of data. The next thread is a calculation thread, it takes the numpy array (or multiple numpy arrays) via a Python Queue.Queue and calculates what will be displayed on the GUI. The calculator then signals the GUI thread (the main thread) via a custom signal and this tells the GUI to update the matplotlib figure that's displayed.
I'm using the "proper" method described here and here.
So here's the general layout. I tried to shorten my typing time and used comments instead of the actual code in some parts:
class Source(QtCore.QObject):
signal_finished = pyQtSignal(...)
def __init__(self, window):
self._exiting = False
self._window = window
def do_stuff(self):
# Start complicated data generator
for data in generator:
if not self._exiting:
# Get data from generator
# Do stuff - add data to Queue
# Loop ends when generator ends
else:
break
# Close complicated data generator
def prepare_exit(self):
self._exiting = True
class Calculator(QtCore.QObject):
signal_finished = pyQtSignal(...)
def __init__(self, window):
self._exiting = False
self._window = window
def do_stuff(self):
while not self._exiting:
# Get stuff from Queue (with timeout)
# Calculate stuff
# Emit signal to GUI
self._window.signal_for_updating.emit(...)
def prepare_exit(self):
self._exiting = True
class GUI(QtCore.QMainWindow):
signal_for_updating = pyQtSignal(...)
signal_closing = pyQtSignal(...)
def __init__(self):
self.signal_for_updating.connect(self.update_handler, type=QtCore.Qt.BlockingQueuedConnection)
# Other normal GUI stuff
def update_handler(self, ...):
# Update GUI
def closeEvent(self, ce):
self.fileQuit()
def fileQuit(self): # Used by a menu I have File->Quit
self.signal_closing.emit() # Is there a builtin signal for this
if __name__ == '__main__':
app = QtCore.QApplication([])
gui = GUI()
gui.show()
source_thread = QtCore.QThread() # This assumes that run() defaults to calling exec_()
source = Source(window)
source.moveToThread(source_thread)
calc_thread = QtCore.QThread()
calc = Calculator(window)
calc.moveToThread(calc_thread)
gui.signal_closing.connect(source.prepare_exit)
gui.signal_closing.connect(calc.prepare_exit)
source_thread.started.connect(source.do_stuff)
calc_thread.started.connect(calc.do_stuff)
source.signal_finished.connect(source_thread.quit)
calc.signal_finished.connect(calc_thread.quit)
source_thread.start()
calc_thread.start()
app.exec_()
source_thread.wait() # Should I do this?
calc_thread.wait() # Should I do this?
...So, my problems all occur when I try to close the GUI before the sources are complete, when I let the data generators finish it closes fine:
While waiting for the threads, the program hangs. As far as I can tell this is because the closing signal's connected slots never get run by the other thread's event loops (they're stuck on the "infinitely" running do_stuff method).
When the calc thread emits the updating gui signal (a BlockedQueuedConnection signal) right after the GUI closing, it seems to hang. I'm guessing this is because the GUI is already closed and isn't there to accept the emitted signal (judging by the print messages I put in my actual code).
I've been looking through tons of tutorials and documentation and I just feel like I'm doing something stupid. Is this possible, to have an event loop and an "infinite" running loop that end early...and safely (resources closed properly)?
I'm also curious about my BlockedQueuedConnection problem (if my description makes sense), however this problem is probably fixable with a simple redesign that I'm not seeing.
Thanks for any help, let me know what doesn't make sense. If it's needed I can also add more to the code instead of just doing comments (I was kind of hoping that I did something dumb and it wouldn't be needed).
Edit: I found some what of a work around, however, I think I'm just lucky that it works every time so far. If I make the prepare_exit and the thread.quit connections DirectConnections, it runs the function calls in the main thread and the program does not hang.
I also figured I should summarize some questions:
Can a QThread have an event loop (via exec_) and have a long running loop?
Does a BlockingQueuedConnection emitter hang if the receiver disconnects the slot (after the signal was emitted, but before it was acknowledged)?
Should I wait for the QThreads (via thread.wait()) after app.exec_(), is this needed?
Is there a Qt provided signal for when QMainWindow closes, or is there one from the QApplication?
Edit 2/Update on progress: I have created a runnable example of the problem by adapting this post to my needs.
from PyQt4 import QtCore
import time
import sys
class intObject(QtCore.QObject):
finished = QtCore.pyqtSignal()
interrupt_signal = QtCore.pyqtSignal()
def __init__(self):
QtCore.QObject.__init__(self)
print "__init__ of interrupt Thread: %d" % QtCore.QThread.currentThreadId()
QtCore.QTimer.singleShot(4000, self.send_interrupt)
def send_interrupt(self):
print "send_interrupt Thread: %d" % QtCore.QThread.currentThreadId()
self.interrupt_signal.emit()
self.finished.emit()
class SomeObject(QtCore.QObject):
finished = QtCore.pyqtSignal()
def __init__(self):
QtCore.QObject.__init__(self)
print "__init__ of obj Thread: %d" % QtCore.QThread.currentThreadId()
self._exiting = False
def interrupt(self):
print "Running interrupt"
print "interrupt Thread: %d" % QtCore.QThread.currentThreadId()
self._exiting = True
def longRunning(self):
print "longRunning Thread: %d" % QtCore.QThread.currentThreadId()
print "Running longRunning"
count = 0
while count < 5 and not self._exiting:
time.sleep(2)
print "Increasing"
count += 1
if self._exiting:
print "The interrupt ran before longRunning was done"
self.finished.emit()
class MyThread(QtCore.QThread):
def run(self):
self.exec_()
def usingMoveToThread():
app = QtCore.QCoreApplication([])
print "Main Thread: %d" % QtCore.QThread.currentThreadId()
# Simulates user closing the QMainWindow
intobjThread = MyThread()
intobj = intObject()
intobj.moveToThread(intobjThread)
# Simulates a data source thread
objThread = MyThread()
obj = SomeObject()
obj.moveToThread(objThread)
obj.finished.connect(objThread.quit)
intobj.finished.connect(intobjThread.quit)
objThread.started.connect(obj.longRunning)
objThread.finished.connect(app.exit)
#intobj.interrupt_signal.connect(obj.interrupt, type=QtCore.Qt.DirectConnection)
intobj.interrupt_signal.connect(obj.interrupt, type=QtCore.Qt.QueuedConnection)
objThread.start()
intobjThread.start()
sys.exit(app.exec_())
if __name__ == "__main__":
usingMoveToThread()
You can see by running this code and swapping between the two connection types on interrupt_signal that the direct connection works because its running in a separate thread, proper or bad practice? I feel like that is bad practice because I am quickly changing something that another thread is reading. The QueuedConnection does not work because the event loop must wait until longRunning is finished before the event loop gets back around to the interrupt signal, which is not what I want.
Edit 3: I remembered reading that QtCore.QCoreApplication.processEvents can be used in cases with long running calculations, but everything I read said don't use it unless you know what you are doing. Well here is what I think it's doing (in a sense) and using it seems to work: When you call processEvents it causes the caller's event loop to halt its current operation and continue on processing the pending events in the event loop, eventually continuing the long calculation event. Other recommendations like in this email suggest timers or putting the work in other threads, I think this just makes my job even more complicated, especially since I've proven(I think) timers don't work in my case. If processEvents seems to fix all my problems I will answer my own question later.

I honestly did not read all of the code. I would recommend against having loops in your code but instead run each logical chunk at a time. Signals/Slots can work as transparent queues for these things too.
Some producer/consumer example code I've written
https://github.com/epage/PythonUtils/blob/master/qt_producer_consumer.py
Some different threading code with more advanced utils I've written
https://github.com/epage/PythonUtils/blob/master/qt_error_display.py
Yes I used loops, mostly for example purposes but sometimes you can't avoid them (like reading from an pipe). You can either use QTimer with a timeout of 0 or have a flag to mark that things should quit and protect it with a mutex.
RE EDIT 1:
1. Don't mix exec_ with long running loops
3. PySide requires that you wait after quitting a thread.
4. I don't remember there being one, you can set it to Destroy On Close and then monitor for close or you can inherit from QMainWindow, override closeEvent and fire a signal (like I do in the qt_error_display.py example)
RE EDIT 2:
I'd recommend using the default connection types.
RE EDIT 3: Don't use processEvents.

After looking through the mailing list archives, google searching, stack overflow searching, and thinking about what my question really was and what the purpose of the question was I came up with this answer:
The short answer being use processEvents(). The long answer is that all my searching results in people saying "be very careful using processEvents()" and "avoid it at all costs". I think it should be avoided if you are using it because you are not seeing results in your GUI main thread fast enough. Instead of using processEvents in this case, the work being done in the main thread that is not UI purposed should be moved to another thread (as my design has done).
The reason my specific problem needs processEvents() is that I want my QThreads to have two way communication with the GUI thread, which means that my QThreads have to have an event loop (exec_()) to accept signals from the GUI. This two way communication is what I meant earlier by "the purpose of the question". Since my QThreads are meant to run "concurrently" with the main GUI thread AND because they need to update the GUI and be "updated" by the GUI (the exit/closing signal in my first example), they need processEvents(). I think this is what processEvents() is for.
My understanding of processEvents(), as decribed above, is that when called in a QThread it will block/pause the current event (my longRunning method) while it continues on through the events in the event loop (only for the QThread processEvents() was called in). After going through the pending events, the event loop wraps back around and continues running the event that it paused (my longRunning method).
I know I didn't answer all my questions, but the main one is answered.
PLEASE CORRECT ME IF I AM WRONG IN ANY WAY
Edit: Please read Ed's answer and the comments.

Instead of QMetaObject.invokeMethod it is also possible to use QTimer with 0 timeout as suggested here: https://doc.qt.io/qt-5/qtimer.html

You could split your workload into chunks and process them one by one in separate slot calls as suggested here: https://wiki.qt.io/Threads_Events_QObjects
import time
import sys
from PyQt5 import QtCore
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QMetaObject, Qt, QThread
class intObject(QtCore.QObject):
finished = pyqtSignal()
interrupt_signal = pyqtSignal()
def __init__(self):
QtCore.QObject.__init__(self)
print("__init__ of interrupt Thread: %d" % QThread.currentThreadId())
QtCore.QTimer.singleShot(3000, self.send_interrupt)
#pyqtSlot()
def send_interrupt(self):
print("send_interrupt Thread: %d" % QThread.currentThreadId())
self.interrupt_signal.emit()
self.finished.emit()
class SomeObject(QtCore.QObject):
finished = pyqtSignal()
def __init__(self):
QtCore.QObject.__init__(self)
print("__init__ of obj Thread: %d" % QThread.currentThreadId())
self._exiting = False
self.count = 0
#pyqtSlot()
def interrupt(self):
print("Running interrupt")
print("interrupt Thread: %d" % QThread.currentThreadId())
self._exiting = True
#pyqtSlot()
def longRunning(self):
if self.count == 0:
print("longrunning Thread: %d" % QThread.currentThreadId())
print("Running longrunning")
if self._exiting:
print('premature exit')
self.finished.emit()
elif self.count < 5:
print(self.count, 'sleeping')
time.sleep(2)
print(self.count, 'awoken')
self.count += 1
QMetaObject.invokeMethod(self, 'longRunning', Qt.QueuedConnection)
else:
print('normal exit')
self.finished.emit()
class MyThread(QThread):
def run(self):
self.exec_()
def usingMoveToThread():
app = QtCore.QCoreApplication([])
print("Main Thread: %d" % QThread.currentThreadId())
# Simulates user closing the QMainWindow
intobjThread = MyThread()
intobj = intObject()
intobj.moveToThread(intobjThread)
# Simulates a data source thread
objThread = MyThread()
obj = SomeObject()
obj.moveToThread(objThread)
obj.finished.connect(objThread.quit)
intobj.finished.connect(intobjThread.quit)
objThread.started.connect(obj.longRunning)
objThread.finished.connect(app.exit)
#intobj.interrupt_signal.connect(obj.interrupt, type=Qt.DirectConnection)
intobj.interrupt_signal.connect(obj.interrupt, type=Qt.QueuedConnection)
objThread.start()
intobjThread.start()
sys.exit(app.exec_())
if __name__ == "__main__":
usingMoveToThread()
Result:
Main Thread: 19940
__init__ of interrupt Thread: 19940
__init__ of obj Thread: 19940
longrunning Thread: 18040
Running longrunning
0 sleeping
0 awoken
1 sleeping
send_interrupt Thread: 7876
1 awoken
Running interrupt
interrupt Thread: 18040
premature exit

Related

Stopping an infinite loop in a worker thread in PyQt5 the simplest way

I intend to have a GUI where one (later three) threads read live data from different sources with an adjustable interval (e.g. 10s) and plot these data in the main window.
I am using PyQt5 and python 3.6.
The reading is performed in an infinite loop in a worker thread as such:
class ReadingThread(QtCore.QObject):
output = QtCore.pyqtSignal(object)
def __init__(self, directory, interval):
QtCore.QObject.__init__(self)
self.directory=directory
self.stillrunning = True
self.refreshtime = interval
def run(self):
print('Entered run in worker thread')
self.stillrunning = True
while self.stillrunning:
outstring=self.read_last_from_logfile() # data reader function, not displayed
self.output.emit(outstring)
time.sleep(self.refreshtime)
#QtCore.pyqtSlot(int) # never called as loop is blocking?
def check_break(self, val):
if val:
self.stillrunning=False
else:
self.stillrunning = True
The main thread looks like this, start() and stop() are called via pushButtons:
class Window(QtWidgets.QMainWindow, MainWindow.Ui_MainWindow):
def __init__(self, directory, interval):
super(Window, self).__init__()
self.thread = QtCore.QThread()
self.worker = ReadingThread(directory, interval)
emit_stop=QtCore.pyqtSignal(int)
def start(self):
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
self.worker.output.connect(self.print_new_value)
self.emit_stop.connect(self.worker.check_break)
self.thread.start()
def stop(self):
self.emit_stop.emit(1)
# time.sleep(11)
if self.thread.isRunning(): #did not work either
self.thread.quit()
if self.thread.isRunning(): #did also not work
self.thread.terminate()
return
def print_new_value(self, value): #test function for output of values read by worker thread, working well
print (value)
return
def main():
app = QtWidgets.QApplication(sys.argv)
interval=10 #read every 10s new data
directory="/home/mdt-user/logfiles/T_P_logs"
gui = Window(directory,interval)
gui.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
My problem is: How can I let the worker thread in the loop look for incoming signals issued by the main thread? Or differently phrased: How can I have a status variable like my self.stillrunning which can be set/accessed outside the worker thread but checked within the worker thread?
I want to avoid killing the thread with means like self.thread.terminate(), which I did unsuccessfully try.
Help would very much be appreciated. I did a search of course, but the answers given and/or problems stated were either too lengthy for what I assume has to be a simple solution, or not applicable.
I don’t see how above comment could have solved your issue. By design your worker will not be able to receive signals, as the while loop blocks the worker event loop until it breaks and the method finishes. Then all the (throughout the blockage) received signals will be worked. Well, technically your received signals aren’t blocked, they’re just not getting worked, until the event loop is being worked again...
I see two solutions, that work with your design pattern (utilizing move to thread).
Solution 1: QTimer (clean, more QT like solution)
The idea here is to use a QTimer. You provide a time period (in milliseconds) to this timer and every time this period is passed, said timer will perform a task (i.e. call a method/function). As you can even pass 0 ms as time period, you can emulate a while loop like behavior. The upside: The event loop won’t be blocked and after every timeout, received signals will be worked.
I modified your code realizing this solution via QTimer. I think with the code comments this example is somewhat self-explanatory.
class ReadingThread(QtCore.QObject):
output = QtCore.pyqtSignal(object)
def __init__(self, directory, interval):
#QtCore.QObject.__init__(self)
super(ReadingThread, self).__init__() # this way is more common to me
self.directory = directory
self.refreshtime = interval
# setting up a timer to substitute the need of a while loop for a
# repetitive task
self.poller = QTimer(self)
# this is the function the timer calls upon on every timeout
self.poller.timeout.connect(self._polling_routine)
def _polling_routine(self):
# this is what's inside of your while loop i.e. your repetitive task
outstring = self.read_last_from_logfile()
self.output.emit(outstring)
def polling_start(self):
# slot to call upon when timer should start the routine.
self.poller.start(self.refreshtime)
# the argument specifies the milliseconds the timer waits in between
# calls of the polling routine. If you want to emulate the polling
# routine in a while loop, you could pass 0 ms...
def polling_stop(self):
# This simply stops the timer. The timer is still "alive" after.
self.poller.stop()
# OR substitute polling_start and polling_stop by toggling like this:
def polling_toggle(self):
poller_active = self.poller.isActive()
if poller_active:
# stop polling
self.poller.stop()
else:
# start polling
self.poller.start(self.refreshtime)
class Window(QtWidgets.QMainWindow, MainWindow.Ui_MainWindow):
emit_start = QtCore.pyqtSignal()
emit_stop = QtCore.pyqtSignal()
def __init__(self, directory, interval):
super(Window, self).__init__()
self.init_worker()
def init_worker(self):
self.thread = QtCore.QThread()
self.worker = ReadingThread(directory, interval)
self.worker.moveToThread(self.thread)
self.worker.output.connect(self.print_new_value)
self.emit_start.connect(self.worker.polling_start)
self.emit_stop.connect(self.worker.polling_stop)
self.thread.start()
def start_polling(self):
self.emit_start.emit()
def stop_polling(self):
self.emit_stop.emit()
def finish_worker(self):
# for sake of completeness: call upon this method if you want the
# thread gone. E.g. before closing your application.
# You could emit a finished sig from your worker, that will run this.
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
def print_new_value(self, value):
print(value)
For a better look at how to do this cleanly with QThread (the complexity here is doing the threading right, the QTimer is relatively trivial): https://realpython.com/python-pyqt-qthread/#using-qthread-to-prevent-freezing-guis
EDIT: Make sure to check out the documentation of the QTimer. You can dynamically set the timeout period and so much more.
Solution 2: Passing a mutable as a control variable
You can e.g. pass a dictionary with a control variable into your worker class/thread and use it to break the loop. This works, as (oversimplified statements follow) threads share common memory and mutable objects in python share the same object in memory (this has been more than thoroughly discussed on SO).
I’ll illustrate this here in your modified code, also illustrated you'll find that the memory id is the same for your control dicts in the main and the worker thread:
class ReadingThread(QtCore.QObject):
output = QtCore.pyqtSignal(object)
def __init__(self, directory, interval, ctrl):
QtCore.QObject.__init__(self)
self.ctrl = ctrl # dict with your control var
self.directory = directory
self.refreshtime = interval
def run(self):
print('Entered run in worker thread')
print('id of ctrl in worker:', id(self.ctrl))
self.ctrl['break'] = False
while True:
outstring=self.read_last_from_logfile()
self.output.emit(outstring)
# checking our control variable
if self.ctrl['break']:
print('break because flag raised')
# might emit finished signal here for proper cleanup
break # or in this case: return
time.sleep(self.refreshtime)
class Window(QtWidgets.QMainWindow, MainWindow.Ui_MainWindow):
emit_stop=QtCore.pyqtSignal(int)
def __init__(self, directory, interval):
super(Window, self).__init__()
self.thread = QtCore.QThread()
self.ctrl = {'break': False} # dict with your control variable
print('id of ctrl in main:', id(self.ctrl))
# pass the dict with the control variable
self.worker = ReadingThread(directory, interval, self.ctrl)
def start(self):
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
self.worker.output.connect(self.print_new_value)
self.thread.start()
def stop(self):
# we simply set the control variable (often refered to as raising a flag)
self.ctrl['break'] = True
This solution almost requires no changes to your code, and I would definitely consider it not QT like or even clean, but it is extremely convenient. And sometimes you don’t want to code your experiment/long running task around the fact that you’re using a GUI toolkit.
This is the only way I know of, that let’s you get around the blocked event loop. If somebody has a cleaner solution to this, please let the world know. Especially as this is the only way, to break out of your long running task in a controlled manner, from multiple points, as you can check for your control variable multiple times throughout your repetitive routine.

Qthread is still working when i close gui on python pyqt

my code has thread, but when i close the gui, it still works on background. how can i stop threads? is there something stop(), close()?
i dont use signal, slots? Must i use this?
from PyQt4 import QtGui, QtCore
import sys
import time
import threading
class Main(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.kac_ders=QtGui.QComboBox()
self.bilgi_cek=QtGui.QPushButton("Save")
self.text=QtGui.QLineEdit()
self.widgetlayout=QtGui.QFormLayout()
self.widgetlar=QtGui.QWidget()
self.widgetlar.setLayout(self.widgetlayout)
self.bilgiler=QtGui.QTextBrowser()
self.bilgi_cek.clicked.connect(self.on_testLoop)
self.scrollArea = QtGui.QScrollArea()
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setWidget(self.widgetlar)
self.analayout=QtGui.QVBoxLayout()
self.analayout.addWidget(self.text)
self.analayout.addWidget(self.bilgi_cek)
self.analayout.addWidget(self.bilgiler)
self.centralWidget=QtGui.QWidget()
self.centralWidget.setLayout(self.analayout)
self.setCentralWidget(self.centralWidget)
def on_testLoop(self):
self.c_thread=threading.Thread(target=self.kontenjan_ara)
self.c_thread.start()
def kontenjan_ara(self):
while(1):
self.bilgiler.append(self.text.text())
time.sleep(10)
app = QtGui.QApplication(sys.argv)
myWidget = Main()
myWidget.show()
app.exec_()
A few things:
You shouldn't be calling GUI code from outside the main thread. GUI elements are not thread-safe. self.kontenjan_ara updates and reads from GUI elements, it shouldn't be the target of your thread.
In almost all cases, you should use QThreads instead of python threads. They integrate nicely with the event and signal system in Qt.
If you just want to run something every few seconds, you can use a QTimer
def __init__(self, parent=None):
...
self.timer = QTimer(self)
self.timer.timeout.connect(self.kontenjan_ara)
self.timer.start(10000)
def kontenjan_ara(self):
self.bilgiler.append(self.text.text())
If your thread operations are more computationally complex you can create a worker thread and pass data between the worker thread and the main GUI thread using signals.
class Worker(QObject):
work_finished = QtCore.pyqtSignal(object)
#QtCore.pyqtSlot()
def do_work(self):
data = 'Text'
while True:
# Do something with data and pass back to main thread
data = data + 'text'
self.work_finished.emit(data)
time.sleep(10)
class MyWidget(QtGui.QWidget):
def __init__(self, ...)
...
self.worker = Worker()
self.thread = QtCore.QThread(self)
self.worker.work_finished.connect(self.on_finished)
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.do_work)
self.thread.start()
#QtCore.pyqtSlot(object)
def on_finished(self, data):
self.bilgiler.append(data)
...
Qt will automatically kill all the subthreads when the main thread exits the event loop.
I chose to rewrite a bit this answer, because I had failed to properly look at the problem's context. As the other answers and comments tell, you code lacks thread-safety.
The best way to fix this is to try to really think "in threads", to restrict yourself to only use objects living in the same thread, or functions that are known as "threadsafe".
Throwing in some signals and slots will help, but maybe you want to think back a bit to your original problem. In your current code, each time a button is pressed, a new thread in launched, that will, every 10 seconds, do 2 things :
- Read some text from self.text
- Append it to self.bilgiler
Both of these operations are non-threadsafe, and must be called from the thread that owns these objects (the main thread). You want to make the worker threads "schedule & wait" the read & append oeprations, instead of simply "executing" them.
I recommend using the other answer (the thread halting problem is automatically fixed by using proper QThreads that integrate well with Qt's event loop), which would make you use a cleaner approach, more integrated with Qt.
You may also want to rethink your problem, because maybe there is a simpler approach to your problem, for example : not spawning threads each time bilgi_cek is clicked, or using Queue objects so that your worker is completely agnostic of your GUI, and only interact with it using threadsafe objects.
Good luck, sorry if I caused any confusion. My original answer is still available here. I think it would be wise to mark the other answer as the valid answer for this question.

Cannot send posted events for objects in another thread

When I try to use one QDialog object from threads I get this error.
Here is the code I'm using:
import threading
import test_qdialog
from PyQt4 import QtGui, QtCore
class MyThread(threading.Thread):
def __init__(self, id, window, mutex):
self.id = id
self.window = window
self.mutex = mutex
super(MyThread, self).__init__()
def run(self):
with self.mutex:
result = self.window.exec_()
if result == QtGui.QDialog.Accepted:
print "Thread %d: %s" % (self.id, self.window.message_input.text())
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
mutex = threading.Lock()
threads = []
window = test_qdialog.MyDialog()
for i in range(5):
thread = MyThread(i, window, mutex)
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
sys.exit(app.exec_())
As written in this answer, if I get it right, I can't do it this way. But how can I do it then?
You can only create and use GUI widgets on main thread (every UI library that I know is like that). However, you can easily pass signals from threads to main using QtCore.QtThread. See for example the answer to PyQt threads and signals - how to properly retrieve values (even if the answer is not what the OP was looking for, it is relevant to your situation). May also find this SO post useful.
So instead of creating or accessing the dialog from thread, you would emit a signal from thread, and have your main window connected to it create the dialog when it receives the signal. Qt takes care of transfering data between threads. Will work like a charm.
Definitely take a close look at Qt Threading Basics, if you haven't already (if you have, may want to post questions about parts you don't understand, there is tons of important info there).
The QT Widgets can't be accessed from a thread which is not the main thread. For example, if you call mainwindow.show(), the program will crash.
However, this can be easily addressed using QThread. The principle is that instead of controlling the e.g., mainwindow directly, we can send a signal to the main thread, letting the main thread to call the show() method. A signal can carry anything, such as a String or Integer.
I'd strongly suggest you to watch this video. It will solve your problem in 10 minutes.

wxPython, Threads, and PostEvent between modules

I'm relatively new to wxPython (but not Python itself), so forgive me if I've missed something here.
I'm writing a GUI application, which at a very basic level consists of "Start" and "Stop" buttons that start and stop a thread. This thread is an infinite loop, which only ends when the thread is stopped. The loop generates messages, which at the moment are just output using print.
The GUI class and the infinite loop (using threading.Thread as a subclass) are held in separate files. What is the best way to get the thread to push an update to something like a TextCtrl in the GUI? I've been playing around with PostEvent and Queue, but without much luck.
Here's some bare bones code, with portions removed to keep it concise:
main_frame.py
import wx
from loop import Loop
class MainFrame(wx.Frame):
def __init__(self, parent, title):
# Initialise and show GUI
# Add two buttons, btnStart and btnStop
# Bind the two buttons to the following two methods
self.threads = []
def onStart(self):
x = Loop()
x.start()
self.threads.append(x)
def onStop(self):
for t in self.threads:
t.stop()
loop.py
class Loop(threading.Thread):
def __init__(self):
self._stop = threading.Event()
def run(self):
while not self._stop.isSet():
print datetime.date.today()
def stop(self):
self._stop.set()
I did, at one point, have it working by having the classes in the same file by using wx.lib.newevent.NewEvent() along these lines. If anyone could point me in the right direction, that'd be much appreciated.
The easiest solution would be to use wx.CallAfter
wx.CallAfter(text_control.SetValue, "some_text")
You can call CallAfter from any thread and the function that you pass it to be called will be called from the main thread.

Thread Finished Event in Python

I have a PyQt program, in this program I start a new thread for drawing a complicated image.
I want to know when the thread has finished so I can print the image on the form.
The only obstacle I'm facing is that I need to invoke the method of drawing from inside the GUI thread, so I want a way to tell the GUI thread to do something from inside the drawing thread.
I could do it using one thread but the program halts.
I used to do it in C# using a BackgroundWorker which had an event for finishing.
Is there a way to do such thing in Python? or should I hack into the main loop of PyQt application and change it a bit?
In the samples with PyQt-Py2.6-gpl-4.4.4-2.exe, there's the Mandelbrot app. In my install, the source is in C:\Python26\Lib\site-packages\PyQt4\examples\threads\mandelbrot.pyw. It uses a thread to render the pixmap and a signal (search the code for QtCore.SIGNAL) to tell the GUI thread its time to draw. Looks like what you want.
I had a similar issue with one of my projects, and used signals to tell my main GUI thread when to display results from the worker and update a progress bar.
Note that there are several examples to connect objects and signals in the PyQt reference guide. Not all of which apply to python (took me a while to realize this).
Here are the examples you want to look at for connecting a python signal to a python function.
QtCore.QObject.connect(a, QtCore.SIGNAL("PySig"), pyFunction)
a.emit(QtCore.SIGNAL("pySig"), "Hello", "World")
Also, don't forget to add __pyqtSignals__ = ( "PySig", ) to your worker class.
Here's a stripped down version of what I did:
class MyGui(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.worker = None
def makeWorker(self):
#create new thread
self.worker = Worker(work_to_do)
#connect thread to GUI function
QtCore.QObject.connect(self.worker, QtCore.SIGNAL('progressUpdated'), self.updateWorkerProgress)
QtCore.QObject.connect(self.worker, QtCore.SIGNAL('resultsReady'), self.updateResults)
#start thread
self.worker.start()
def updateResults(self):
results = self.worker.results
#display results in the GUI
def updateWorkerProgress(self, msg)
progress = self.worker.progress
#update progress bar and display msg in status bar
class Worker(QtCore.QThread):
__pyqtSignals__ = ( "resultsReady",
"progressUpdated" )
def __init__(self, work_queue):
self.progress = 0
self.results = []
self.work_queue = work_queue
QtCore.QThread.__init__(self, None)
def run(self):
#do whatever work
num_work_items = len(self.work_queue)
for i, work_item in enumerate(self.work_queue):
new_progress = int((float(i)/num_work_items)*100)
#emit signal only if progress has changed
if self.progress != new_progress:
self.progress = new_progress
self.emit(QtCore.SIGNAL("progressUpdated"), 'Working...')
#process work item and update results
result = processWorkItem(work_item)
self.results.append(result)
self.emit(QtCore.SIGNAL("resultsReady"))
I believe that your drawing thread can send an event to the main thread using QApplication.postEvent. You just need to pick some object as the receiver of the event. More info
Expanding on Jeff's answer: the Qt documentation on thread support states that it's possible to make event handlers (slots in Qt parlance) execute in the thread that "owns" an object.
So in your case, you'd define a slot printImage(QImage) on the form, and a doneDrawing(QImage) signal on whatever is creating the image, and just connect them using a queued or auto connection.

Categories