I have several threads that need to work with window. Here's thread definition:
class MyThread(QtCore.QThread):
def __init__(self, id, window, mutex):
super(MyThread, self).__init__()
self.id = id
self.window = window
self.mutex = mutex
self.connect(self, QtCore.SIGNAL("load_message_input()"), self.window, QtCore.SLOT("show_input()"))
def run(self):
self.mutex.lock()
self.emit(QtCore.SIGNAL("load_message_input()"))
self.connect(self.window, QtCore.SIGNAL("got_message(QString)"), self.print_message)
self.window.input_finished.wait(self.mutex)
self.mutex.unlock()
def print_message(self, str):
print "Thread %d: %s" % (self.id, str)
And here's window definition:
class MyDialog(QtGui.QDialog):
def __init__(self, *args, **kwargs):
super(MyDialog, self).__init__(*args, **kwargs)
self.last_message = None
self.setModal(True)
self.message_label = QtGui.QLabel(u"Message")
self.message_input = QtGui.QLineEdit()
self.dialog_buttons = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
self.dialog_buttons.accepted.connect(self.accept)
self.dialog_buttons.rejected.connect(self.reject)
self.hbox = QtGui.QHBoxLayout()
self.hbox.addWidget(self.message_label)
self.hbox.addWidget(self.message_input)
self.vbox = QtGui.QVBoxLayout()
self.vbox.addLayout(self.hbox)
self.vbox.addWidget(self.dialog_buttons)
self.setLayout(self.vbox)
self.input_finished = QtCore.QWaitCondition()
#QtCore.pyqtSlot()
def show_input(self):
self.exec_()
def on_accepted(self):
self.emit(QtCore.SIGNAL("got_message(QString)"), self.message_input.text())
self.input_finished.wakeOne()
And here's main:
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
mutex = QtCore.QMutex()
threads = []
window = test_qdialog.MyDialog()
for i in range(5):
thread = MyThread(i, window, mutex)
thread.start()
threads.append(thread)
for t in threads:
t.wait()
sys.exit(app.exec_())
I can't figure out why window isn't shown when executing the script.
Update:
For some reason other threads don't stop on line with self.mutex.lock(). Can't figure out why.
You have several problems in your code:
If you want a QThread to use slots you need to create an event loop for it (which is easy, just call QThread.exec_), but QThreads with event loops needs to be coded differently (next I'll post you an example)
You need to connect on_accepted to accepted if you want to emit the messages, unless you use the auto-connect features of Qt.
If you want to use QThread first you need to start a QApplication so for t in threads: t.wait() can't be executed before the call to QApplication.exec_ (in my example just removed it).
The last but not less important issue: If you want your threads to consume resources exclusively you should think of a consumer-producer approach (the problem is that when you emit a signal every slot will get a copy of the data and if you try to block a thread with an event loop the application just freezes, to solve the problem of consumer-producer I pass an extra mutex to the signal of the message and try to lock it [never blocking!] to know if the thread con consume the event)
As promised there is an example of how to use event loops on QThreads:
from PyQt4 import QtCore, QtGui
class MyThread(QtCore.QThread):
load_message_input = QtCore.pyqtSignal()
def __init__(self, id, window):
super(MyThread, self).__init__()
self.id = id
self.window = window
self.load_message_input.connect(self.window.show_input)
self.window.got_message.connect(self.print_message)
self.started.connect(self.do_stuff)
def run(self):
print "Thread %d: %s" % (self.id,"running")
self.exec_()
#QtCore.pyqtSlot()
def do_stuff(self):
print "Thread %d: %s" % (self.id,"emit load_message_input")
self.load_message_input.emit()
#QtCore.pyqtSlot("QString","QMutex")
def print_message(self, msg, mutex):
if mutex.tryLock():
print "Thread %d: %s" % (self.id, msg)
self.do_stuff()
class MyDialog(QtGui.QDialog):
got_message = QtCore.pyqtSignal("QString","QMutex")
def __init__(self, *args, **kwargs):
super(MyDialog, self).__init__(*args, **kwargs)
self.last_message = None
self.setModal(True)
self.message_label = QtGui.QLabel(u"Message")
self.message_input = QtGui.QLineEdit()
self.dialog_buttons = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
self.dialog_buttons.accepted.connect(self.accept)
self.dialog_buttons.accepted.connect(self.on_accepted)
self.dialog_buttons.rejected.connect(self.reject)
self.hbox = QtGui.QHBoxLayout()
self.hbox.addWidget(self.message_label)
self.hbox.addWidget(self.message_input)
self.vbox = QtGui.QVBoxLayout()
self.vbox.addLayout(self.hbox)
self.vbox.addWidget(self.dialog_buttons)
self.setLayout(self.vbox)
self.input_finished = QtCore.QWaitCondition()
#QtCore.pyqtSlot()
def show_input(self):
print "showing input"
window.show()
window.setModal(True)
#QtCore.pyqtSlot()
def on_accepted(self):
print "emit: ", self.message_input.text()
self.got_message.emit(self.message_input.text(), QtCore.QMutex())
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
mutex = QtCore.QMutex()
threads = []
window = MyDialog()
for i in range(5):
thread = MyThread(i, window)
thread.start()
threads.append(thread)
print "start app"
sys.exit(app.exec_())
Note: almost always the thread who receives the signal first will be the one with id 1.
My recommendation, do not use slots in your threads (which will make safe the use of mutex and wait-conditions) and implement a consumer-producer approach for the messages.
You are waiting for the threads to exit, before calling app.exec_(). You should probably monitor the threads in a GUI idle loop or connect to the thread's finished() signal.
Related
I'm trying to find a way to quit my app properly. When I exit, I get an error saying QThread: Destroyed while thread is still running. I have a thread for feeding output to a QTextBrowser. What should be the proper way to exit? Here's what I've got:
class LogReceiver(QtCore.QObject):
mysignal = QtCore.Signal(str)
def __init__(self, queue, *args, **kwargs):
QtCore.QObject.__init__(self, *args, **kwargs)
self.queue = queue
def run(self):
while True:
text = self.queue.get()
self.mysignal.emit(text)
if __name__ == '__main__':
queue = Queue()
thread = QtCore.QThread()
my_receiver = MyReceiver(queue)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
my_receiver.mysignal.connect(window.append_text)
my_receiver.moveToThread(thread)
thread.started.connect(my_receiver.run)
thread.start()
sys.exit(app.exec_())
Should thread somehow be terminated upon exit? Note that self.queue.get() blocks and waits for text.
Thanks
You need to re-structure the while loop so that it doesn't block uncondtionally.
You can do this with a simple flag and a timeout:
def run(self):
self.active = True
while self.active:
try:
text = self.queue.get(timeout=1.0)
self.mysignal.emit(text)
except Empty:
continue
So now the queue won't block indefinitely, and the flag will be checked once a second to see if the loop should be exited.
EDIT:
Here's a working example based on your code:
import sys
from queue import Queue, Empty
from PySide import QtCore, QtGui
class LogReceiver(QtCore.QObject):
mysignal = QtCore.Signal(str)
def __init__(self, queue, *args, **kwargs):
QtCore.QObject.__init__(self, *args, **kwargs)
self.queue = queue
def run(self):
self.active = True
while self.active:
try:
text = self.queue.get(timeout=1.0)
self.mysignal.emit('text')
except Empty:
continue
print('finished')
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.queue = Queue()
self.thread = QtCore.QThread(self)
self.receiver = LogReceiver(self.queue)
self.receiver.moveToThread(self.thread)
self.thread.started.connect(self.receiver.run)
self.thread.start()
def closeEvent(self, event):
print('close')
self.receiver.active = False
self.thread.quit()
self.thread.wait()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
Try:
# previous code here
thread.start()
app.exec_()
thread.terminate()
thread.wait()
sys.exit(0)
Basically when exec_() finishes (QApplication is closed ex. by closing the window) you force the thread to terminate and wait() for it to cleanup. If your thread has an event loop you can call quit() instead of terminate(). terminate() is generally not a good idea see: here.
The more desirable approach would be to put a flag in run() method ex.
while !flag:
do stuff
and change main to:
app.exec_()
flag = True
thread.wait()
sys.exit(0)
Where flag is a global variable. QThread terminates itself when run() method finishes.
I am building a small GUI application which runs a producer (worker) and the GUI consumes the output on demand and plots it (using pyqtgraph).
Since the producer is a blocking function (takes a while to run), I (supposedly) moved it to its own thread.
When calling QThread.currentThreadId() from the producer it outputs the same number as the main GUI thread. So, the worker is executed first, and then all the plotting function calls are executed (because they are being queued on the same thread's event queue). How can I fix this?
Example run with partial:
gui thread id 140665453623104
worker thread id: 140665453623104
Here is my full code:
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import pyqtSignal
import pyqtgraph as pg
import numpy as np
from functools import partial
from Queue import Queue
import math
import sys
import time
class Worker(QtCore.QObject):
termino = pyqtSignal()
def __init__(self, q=None, parent=None):
super(Worker, self).__init__(parent)
self.q = q
def run(self, m=30000):
print('worker thread id: {}'.format(QtCore.QThread.currentThreadId()))
for x in xrange(m):
#y = math.sin(x)
y = x**2
time.sleep(0.001) # Weird, plotting stops if this is not present...
self.q.put((x,y,y))
print('Worker finished')
self.termino.emit()
class MainWindow(QtGui.QWidget):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.q = Queue()
self.termino = False
self.worker = Worker(self.q)
self.workerThread = None
self.btn = QtGui.QPushButton('Start worker')
self.pw = pg.PlotWidget(self)
pi = self.pw.getPlotItem()
pi.enableAutoRange('x', True)
pi.enableAutoRange('y', True)
self.ge1 = pi.plot(pen='y')
self.xs = []
self.ys = []
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.pw)
layout.addWidget(self.btn)
self.resize(400, 400)
def run(self):
self.workerThread = QtCore.QThread()
self.worker.moveToThread(self.workerThread)
self.worker.termino.connect(self.setTermino)
# moveToThread doesn't work here
self.btn.clicked.connect(partial(self.worker.run, 30000))
# moveToThread will work here
# assume def worker.run(self): instead of def worker.run(self, m=30000)
# self.btn.clicked.connect(self.worker.run)
self.btn.clicked.connect(self.graficar)
self.workerThread.start()
self.show()
def setTermino(self):
self.termino = True
def graficar(self):
if not self.q.empty():
e1,e2,ciclos = self.q.get()
self.xs.append(ciclos)
self.ys.append(e1)
self.ge1.setData(y=self.ys, x=self.xs)
if not self.termino:
QtCore.QTimer.singleShot(1, self.graficar)
if __name__ == '__main__':
app = QtGui.QApplication([])
window = MainWindow()
QtCore.QTimer.singleShot(0, window.run);
sys.exit(app.exec_())
The problem is that Qt attempts to choose the connection type (when you call signal.connect(slot)) based on the what thread the slot exists in. Because you have wrapped the slot in the QThread with partial, the slot you are connecting to resides in the MainThread (the GUI thread). You can override the connection type (as the second argument to connect() but that doesn't help because the method created by partial will always exist in the MainThread, and so setting the connection type to by Qt.QueuedConnection doesn't help.
The only way around this that I can see is to set up a relay signal, the sole purpose of which is to effectively change an emitted signal with no arguments (eg the clicked signal from a button) to a signal with one argument (your m parameter). This way you don't need to wrap the slot in the QThread with partial().
The code is below. I've created a signal with one argument (an int) called 'relay' in the main windows class. The button clicked signal is connected to a method within the main window class, and this method has a line of code which emits the custom signal I created. You can extend this method (relay_signal()) to get the integer to pass to the QThread as m (500 in this case), from where ever you like!
So here is the code:
from functools import partial
from Queue import Queue
import math
import sys
import time
class Worker(QtCore.QObject):
termino = pyqtSignal()
def __init__(self, q=None, parent=None):
super(Worker, self).__init__(parent)
self.q = q
def run(self, m=30000):
print('worker thread id: {}'.format(QtCore.QThread.currentThreadId()))
for x in xrange(m):
#y = math.sin(x)
y = x**2
#time.sleep(0.001) # Weird, plotting stops if this is not present...
self.q.put((x,y,y))
print('Worker finished')
self.termino.emit()
class MainWindow(QtGui.QWidget):
relay = pyqtSignal(int)
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.q = Queue()
self.termino = False
self.worker = Worker(self.q)
self.workerThread = None
self.btn = QtGui.QPushButton('Start worker')
self.pw = pg.PlotWidget(self)
pi = self.pw.getPlotItem()
pi.enableAutoRange('x', True)
pi.enableAutoRange('y', True)
self.ge1 = pi.plot(pen='y')
self.xs = []
self.ys = []
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.pw)
layout.addWidget(self.btn)
self.resize(400, 400)
def run(self):
self.workerThread = QtCore.QThread()
self.worker.termino.connect(self.setTermino)
self.worker.moveToThread(self.workerThread)
# moveToThread doesn't work here
# self.btn.clicked.connect(partial(self.worker.run, 30000))
# moveToThread will work here
# assume def worker.run(self): instead of def worker.run(self, m=30000)
#self.btn.clicked.connect(self.worker.run)
self.relay.connect(self.worker.run)
self.btn.clicked.connect(self.relay_signal)
self.btn.clicked.connect(self.graficar)
self.workerThread.start()
self.show()
def relay_signal(self):
self.relay.emit(500)
def setTermino(self):
self.termino = True
def graficar(self):
if not self.q.empty():
e1,e2,ciclos = self.q.get()
self.xs.append(ciclos)
self.ys.append(e1)
self.ge1.setData(y=self.ys, x=self.xs)
if not self.termino or not self.q.empty():
QtCore.QTimer.singleShot(1, self.graficar)
if __name__ == '__main__':
app = QtGui.QApplication([])
window = MainWindow()
QtCore.QTimer.singleShot(0, window.run);
sys.exit(app.exec_())
I also modified the graficar method to continue plotting (even after the thread is terminated) if there is still data in the queue. I think this might be why you needed the time.sleep in the QThread, which is now also removed.
Also regarding your comments in the code on where to place moveToThread, where it is now is correct. It should be before the call that connects the QThread slot to a signal, and the reason for this is discussed in this stack-overflow post: PyQt: Connecting a signal to a slot to start a background operation
I have the following two files:
import sys
import time
from PyQt4 import QtGui, QtCore
import btnModule
class WindowClass(QtGui.QWidget):
def __init__(self):
super(WindowClass, self).__init__()
self.dataLoaded = None
# Widgets
# Buttons
thread = WorkerForLoop(self.runLoop)
# thread.start()
self.playBtn = btnModule.playpauselBtnClass \
('Play', thread.start)
# Layout
layout = QtGui.QHBoxLayout()
layout.addWidget(self.playBtn)
self.setLayout(layout)
# Window Geometry
self.setGeometry(100, 100, 100, 100)
def waitToContinue(self):
print self.playBtn.text()
while (self.playBtn.text() != 'Pause'):
pass
def runLoop(self):
for ii in range(100):
self.waitToContinue()
print 'num so far: ', ii
time.sleep(0.5)
class WorkerForLoop(QtCore.QThread):
def __init__(self, function, *args, **kwargs):
super(WorkerForLoop, self).__init__()
self.function = function
self.args = args
self.kwargs = kwargs
def __del__(self):
self.wait()
def run(self):
print 'let"s run it'
self.function(*self.args, **self.kwargs)
return
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
wmain = WindowClass()
wmain.show()
sys.exit(app.exec_())
and the second file btnModule.py:
from PyQt4 import QtGui, QtCore
class playpauselBtnClass(QtGui.QPushButton):
btnSgn = QtCore.pyqtSignal()
def __init__(self, btnName, onClickFunction):
super(playpauselBtnClass, self).__init__(btnName)
self.clicked.connect(self.btnPressed)
self.btnSgn.connect(onClickFunction)
def btnPressed(self):
if self.text() == 'Play':
self.setText('Pause')
self.btnSgn.emit()
print 'Changed to pause and emited signal'
elif self.text() == 'Pause':
self.setText('Continue')
print 'Changed to Continue'
elif self.text() == 'Continue':
self.setText('Pause')
print 'Changed to Pause'
If in the first file I remove the comment at thread.start() it works as expected, it starts the thread and then hangs until I click Play on the UI. However, I thought it should work even if I didn't start it there as the signal is btnSgn is connected to onClickFunction which in this case takes the value thread.start.
Used this as a reference
http://joplaete.wordpress.com/2010/07/21/threading-with-pyqt4/
I think the reason it doesn't work when you try to call thread.start() in your second file (via self.btnSgn.emit()) is that the thread object goes out of scope outside of the init function where you created it. So you are calling start() on an already deleted thread.
Just changing thread -> self.thread (i.e. making the thread object a member of the WindowClass object) works fine when I tried it, since thread is then kept alive till the end of the program.
I have i python application, which uses PyQt GUI. It application has some I/O operations, which i want to run in separate threads.
When each thread started, it should write messages to applications main window status bar and when last thread is completed, status bar messages should be cleared.
How can i handle number of threads via QThread?
Here is example of code:
import sys, time
from PyQt4 import QtCore, QtGui
from functools import partial
def io_emulator(sleep_seconds):
print 'We will sleep for %s seconds' % str(sleep_seconds)
time.sleep(sleep_seconds)
class IOThread(QtCore.QThread):
def __init__(self, func):
QtCore.QThread.__init__(self)
self.io_func = func
def run(self):
self.io_func()
class m_Window(QtGui.QWidget):
def __init__(self):
super(m_Window, self).__init__()
self.initUI()
def initUI(self):
self.thread_button = QtGui.QPushButton("Thread", self)
self.thread_button.move(30, 10)
self.spinbox = QtGui.QSpinBox(self)
self.spinbox.move(30, 50)
self.stat_label = QtGui.QLabel("", self)
self.stat_label.setGeometry(QtCore.QRect(200, 200, 150, 14))
self.stat_label.move(30,90)
self.setWindowTitle('Threads')
self.show()
self.thread_button.clicked.connect(self._sleeper)
def _sleeper(self):
seconds = int(self.spinbox.text())
stat_str = 'Sleeping %s seconds' % str(seconds)
io_func = partial(io_emulator, seconds)
set_status_f = partial(self.set_status_msg, stat_str)
self.thread = IOThread(io_func)
self.thread.started.connect(set_status_f)
self.thread.finished.connect(self.clear_status_msg)
self.thread.start()
def set_status_msg(self, msg):
self.stat_label.setText(msg)
def clear_status_msg(self):
self.stat_label.clear()
def main():
app = QtGui.QApplication(sys.argv)
m = m_Window()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I want, that message is being cleared only when last thread is ended.
You cannot call QWidget functions from any thread but the main thread. If you want another thread to trigger GUI operations, then they should be communicated by emitting signals that are connected to your main thread:
def someFunc():
return "message"
class IOThread(QtCore.QThread):
statusUpdate = QtCore.pyqtSignal(str)
def __init__(self, func):
QtCore.QThread.__init__(self)
self.io_func = func
def run(self):
msg = self.io_func()
self.statusUpdate.emit(msg)
Then in your main thread, connect the io thread to the status label or some intermediate handler:
io_thread = IOThread(someFunc)
io_thread.statusUpdate.connect(self.status_label.setText)
This will create a queued connection, which places the call into the event loop of the main thread for execution.
I'm trying to learn how to use threading in a python program. I'm using PySide and QThreads since I'm going to implement gui afterwards with PySide.
I have understood the main consept of threading, at least I think. But I'm still confused with event loops. And I think that is the problem with my aplication.
Here is a sample application that I can't get to work properly.
In my main class I have several worker threads and I want to them to report their progress to the main main class. But the main program don't print progress messages in real time.
How could I get this to work?
from PySide import QtCore
import time, sys
class MyWorkerThread(QtCore.QThread):
message = QtCore.Signal(str)
def __init__(self, id, parent=None):
super(MyWorkerThread, self).__init__(parent)
self.id = id
def run(self):
for i in range(10):
self.message.emit("%d: %d" % (self.id, i))
time.sleep(0.2)
class MainProgram():
def __init__(self, parent=None):
self.threads = []
self.addWorker(MyWorkerThread(1))
self.addWorker(MyWorkerThread(2))
def addWorker(self, worker):
worker.message.connect(self.printMessage, QtCore.Qt.QueuedConnection)
self.threads.append(worker)
def startWorkers(self):
for worker in self.threads:
worker.start()
worker.wait()
self.workersFinished()
def workersFinished(self):
QtCore.QCoreApplication.instance().quit()
#QtCore.Slot(str)
def printMessage(self, text):
sys.stdout.write(text+'\n')
sys.stdout.flush()
if __name__ == '__main__':
app = QtCore.QCoreApplication(sys.argv)
m = MainProgram()
m.startWorkers()
sys.exit(app.exec_())
worker.wait() is the problem. This call blocks the main thread (in this case the one running event loop) until the worker finishes its job.
Here is a slightly changed version (I've commented my changes):
from PySide import QtCore
import time, sys
class MyWorkerThread(QtCore.QThread):
message = QtCore.Signal(str)
def __init__(self, id, parent=None):
super(MyWorkerThread, self).__init__(parent)
self.id = id
def run(self):
for i in range(10):
self.message.emit("%d: %d" % (self.id, i))
time.sleep(0.2)
class MainProgram():
def __init__(self, parent=None):
self.threads = []
self.addWorker(MyWorkerThread(1))
self.addWorker(MyWorkerThread(2))
def addWorker(self, worker):
worker.message.connect(self.printMessage, QtCore.Qt.QueuedConnection)
# connect the finished signal to method so that we are notified
worker.finished.connect(self.workersFinished)
self.threads.append(worker)
def startWorkers(self):
for worker in self.threads:
worker.start()
# no wait, no finished. you start the threads and leave.
def workersFinished(self):
if all(worker.isFinished() for worker in self.threads):
# wait until all the threads finished
QtCore.QCoreApplication.instance().quit()
#QtCore.Slot(str)
def printMessage(self, text):
sys.stdout.write(text+'\n')
sys.stdout.flush()
if __name__ == '__main__':
app = QtCore.QCoreApplication(sys.argv)
m = MainProgram()
m.startWorkers()
sys.exit(app.exec_())