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.
Related
When User clicks on "Update" Push Button, box_updatetool_fcn will be executed. MainThread will update the Progressbar in the QMainWindow while the other QThread will download the file. But it executes perfectly, but the problem is signal emited by the WorkerThread is not updating immediately.
Even I have gone through many questions, none solved my problem. I don't know why my fcn_qthread_output is not executing immediately after Qthread finished.
Even the QThread finished() function also executing after the Main Function finished, which I am not using in my current program. I don't know what's wrong in the program, is something missing?
Here is the following console output -
Run Function Closed
Out1 : False, FileName
Main Function Ended
Out2 : True, FileName
What I am expecting is -
Run Function Closed
Out2 : True, FileName
Out1 : True, FileName
Main Function Ended
Below is the program execution flow -
class WorkerThread(QtCore.QThread):
outResult = QtCore.pyqtSignal(tuple)
def __init__(self,target,args,parent=None):
super(WorkerThread, self).__init__(parent)
self.fcn = target
self.args = args
def run(self):
outResult = self.fcn(*self.args)
self.outResult.emit(outResult)
print('Run Function Closed')
class ApplicationWindow(QtWidgets.QMainWindow):
def box_updatetool_fcn(self):
t1 = WorkerThread(target=self.boxapi.fcn_downloadfile, args=(fileID,)) #ThreadWithResult
t1.outResult.connect(self.fcn_qthread_output)
t1.start()
self.box_pbar_fcn(tempfilename,fsize)
print(f'Out1 : {self.var_box_output}')
self.boxapi.fcn_updatetool(file_fullpath,self.progpath)
print('Main Function Ended')
def fcn_qthread_output(self,result):
self.var_box_output = result
print(f'Out2 : {self.var_box_output}')
def box_pbar_fcn(self,filename,fullsize):
pobj = self.ui.progressbar
while(barprogress < 100):
if os.path.exists(filename):
currfilesize = os.path.getsize(filename)
barprogress = int(currfilesize/fullsize*100)
pobj.setValue(barprogress)
Reproducible Code -
I have used box_pbar_fcn (progress bar which shows the downloading progress) and t1 Thread (downloading the file) will Run in simultaneously.
import os, sys
from PyQt5 import QtWidgets, QtGui, QtCore
class WorkerThread(QtCore.QThread):
outResult = QtCore.pyqtSignal(tuple)
def __init__(self,parent=None):
super(WorkerThread, self).__init__(parent)
def run(self):
outResult = (True,'Out1')
self.outResult.emit(outResult)
print('Run Function Closed')
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.var_box_output = (False,'Inp1')
self.box_updatetool_fcn()
def box_updatetool_fcn(self):
tempfilename,fsize = 'Test',300
t1 = WorkerThread() #ThreadWithResult
t1.outResult.connect(self.fcn_qthread_output)
t1.start()
self.box_pbar_fcn(tempfilename,fsize)
print(f'Out1 : {self.var_box_output}')
print('Main Function Ended')
def fcn_qthread_output(self,result):
self.var_box_output = result
print(f'Out2 : {self.var_box_output}')
def box_pbar_fcn(self,filename,fullsize):
pass
#pobj = self.ui.progressbar
#while(barprogress < 100):
#if os.path.exists(filename):
# currfilesize = os.path.getsize(filename)
# barprogress = int(currfilesize/fullsize*100)
# pobj.setValue(barprogress)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mw = ApplicationWindow()
mw.show()
sys.exit(app.exec_())
The problem is that you are blocking the main thread with your while True (There should never be an instruction that blocks the eventloop for long). In Qt the logic is to do the job asynchronously. In this case I suspect that in a thread you are downloading a file and you want to show the download progress in the QProgressBar and for this you are continuously measuring the size of the file. If so, a possible solution is to use a QTimer to run the periodic task of measuring the file size.
Disclaimer: The following code that has not been tested should work. As I do not know how is the way you save the file, it could be that it does not work since many functions only write the file at the end and not in parts for efficiency reasons.
import os
import sys
from functools import cached_property
from PyQt5 import QtCore, QtGui, QtWidgets
class Downloader(QtCore.QThread):
outResult = QtCore.pyqtSignal(tuple)
def run(self):
import time
time.sleep(10)
outResult = (True, "Out1")
self.outResult.emit(outResult)
print("Run Function Closed")
class FileObserver(QtCore.QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._filename = ""
self._current_size = 0
#property
def filename(self):
return self._filename
def start(self, filename, interval=100):
self._current_size = 0
self._filename = filename
self.timer.setInterval(interval)
self.timer.start()
def stop(self):
self.timer.stop()
#cached_property
def timer(self):
timer = QtCore.QTimer()
timer.timeout.connect(self._measure)
return timer
def _measure(self):
if os.path.exists(self.filename):
file_size = os.path.getsize(filename)
if self._current_size != file_size:
self._current_size = file_size
self.file_size_changed.emit(file_size)
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.var_box_output = (False, "Inp1")
self.box_updatetool_fcn()
#cached_property
def file_observer(self):
return FileObserver()
def box_updatetool_fcn(self):
tempfilename, fullsize = "Test", 300
self.ui.progressbar.setMaximum(fullsize)
t1 = WorkerThread(self)
t1.outResult.connect(self.fcn_qthread_output)
t1.finished.connect(self.file_observer.stop)
t1.start()
self.file_observer.start(tempfilename, interval=10)
def fcn_qthread_output(self, result):
self.var_box_output = result
print(f"Out2 : {self.var_box_output}")
def box_pbar_fcn(self, file_size):
self.ui.progressbar.setValue(file_size)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mw = ApplicationWindow()
mw.show()
sys.exit(app.exec_())
I am trying to load some data which takes 30+ seconds. During this time I wish the user to see a small GUI which says "Loading .", then "Loading ..", then "Loading ...", then "Loading ." etc. I have done some reading and I think I have to put this in a separate thread. I found someone who had a similar problem suggesting the solution was this in the right spot:
t = threading.Thread(target=self.test)
t.daemon = True
t.start()
In a lower part of the file I have the test function
def test(self):
tmp = InfoMessage()
while True:
print(1)
and the InfoMessage function
from PyQt5 import uic, QtCore, QtGui, QtWidgets
import sys
class InfoMessage(QtWidgets.QDialog):
def __init__(self, msg='Loading ', parent=None):
try:
super(InfoMessage, self).__init__(parent)
uic.loadUi('ui files/InfoMessage.ui',self)
self.setWindowTitle(' ')
self.o_msg = msg
self.msg = msg
self.info_label.setText(msg)
self.val = 0
self.timer = QtCore.QTimer()
self.timer.setInterval(500)
self.timer.timeout.connect(self.update_message)
self.timer.start()
self.show()
except BaseException as e:
print(str(e))
def update_message(self):
self.val += 1
self.msg += '.'
if self.val < 20:
self.info_label.setText(self.msg)
else:
self.val = 0
self.msg = self.o_msg
QtWidgets.QApplication.processEvents()
def main():
app = QtWidgets.QApplication(sys.argv) # A new instance of QApplication
form = InfoMessage('Loading ') # We set the form to be our MainWindow (design)
app.exec_() # and execute the app
if __name__ == '__main__': # if we're running file directly and not importing it
main() # run the main function
When I run the InfoMessage function alone it works fine and it updates every 0.5 seconds etc. However, when I fun this as part of the loading file the GUI is blank and incorrectly displayed. I know it is staying in the test function because of the print statement in there.
Can someone point me in the right direction? I think I am missing a couple of steps.
First, there are two ways of doing this. One way is to use the Python builtin threading module. The other way is to use the QThread library which is much more integrated with PyQT. Normally, I would recommend using QThread to do threading in PyQt. But QThread is only needed when there is any interaction with PyQt.
Second, I've removed processEvents() from InfoMessage because it does not serve any purpose in your particular case.
Finally, setting your thread as daemon implies your thread will never stop. This is not the case for most functions.
import sys
import threading
import time
from PyQt5 import uic, QtCore, QtWidgets
from PyQt5.QtCore import QThread
def long_task(limit=None, callback=None):
"""
Any long running task that does not interact with the GUI.
For instance, external libraries, opening files etc..
"""
for i in range(limit):
time.sleep(1)
print(i)
if callback is not None:
callback.loading_stop()
class LongRunning(QThread):
"""
This class is not required if you're using the builtin
version of threading.
"""
def __init__(self, limit):
super().__init__()
self.limit = limit
def run(self):
"""This overrides a default run function."""
long_task(self.limit)
class InfoMessage(QtWidgets.QDialog):
def __init__(self, msg='Loading ', parent=None):
super(InfoMessage, self).__init__(parent)
uic.loadUi('loading.ui', self)
# Initialize Values
self.o_msg = msg
self.msg = msg
self.val = 0
self.info_label.setText(msg)
self.show()
self.timer = QtCore.QTimer()
self.timer.setInterval(500)
self.timer.timeout.connect(self.update_message)
self.timer.start()
def update_message(self):
self.val += 1
self.msg += '.'
if self.val < 20:
self.info_label.setText(self.msg)
else:
self.val = 0
self.msg = self.o_msg
def loading_stop(self):
self.timer.stop()
self.info_label.setText("Done")
class MainDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super(MainDialog, self).__init__(parent)
# QThread Version - Safe to use
self.my_thread = LongRunning(limit=10)
self.my_thread.start()
self.my_loader = InfoMessage('Loading ')
self.my_thread.finished.connect(self.my_loader.loading_stop)
# Builtin Threading - Blocking - Do not use
# self.my_thread = threading.Thread(
# target=long_task,
# kwargs={'limit': 10}
# )
# self.my_thread.start()
# self.my_loader = InfoMessage('Loading ')
# self.my_thread.join() # Code blocks here
# self.my_loader.loading_stop()
# Builtin Threading - Callback - Use with caution
# self.my_loader = InfoMessage('Loading ')
# self.my_thread = threading.Thread(
# target=long_task,
# kwargs={'limit': 10,
# 'callback': self.my_loader}
# )
# self.my_thread.start()
def main():
app = QtWidgets.QApplication(sys.argv)
dialog = MainDialog()
app.exec_()
if __name__ == '__main__':
main()
Feel free to ask any follow up questions regarding this code.
Good Luck.
Edit:
Updated to show how to run code on thread completion. Notice the new parameter added to long_task function.
import time
import sys, threading
from PyQt4 import QtGui, QtCore
from PyQt4.QtGui import QApplication
class Global:
def __init__(self):
for c in MyClass, MainWindow:
cl = c()
setattr(self, c.__name__, cl)
setattr(cl, 'global_', self)
class MyClass:
def work(self):
for r in range(100):
if r == 2:
self.global_.MainWindow.SignalBox.emit('MyClass NO PAUSE') # need pause !!!
else:
print(r)
time.sleep(1)
class MainWindow(QtGui.QWidget):
Signal = QtCore.pyqtSignal(str)
SignalBox = QtCore.pyqtSignal(str)
def __init__(self):
QtGui.QWidget.__init__(self)
self.resize(300, 300)
self.lab = QtGui.QLabel()
lay = QtGui.QGridLayout()
lay.addWidget(self.lab)
self.setLayout(lay)
self.msgBox = lambda txt: getattr(QtGui.QMessageBox(), 'information')(self, txt, txt)
self.Signal.connect(self.lab.setText)
self.SignalBox.connect(self.msgBox)
def thread_no_wait(self):
self.global_.MainWindow.SignalBox.emit('MyClass PAUSE OK')
threading.Thread(target=self.global_.MyClass.work).start()
def thread_main(self):
def my_work():
for r in range(100):
self.Signal.emit(str(r))
time.sleep(1)
threading.Thread(target=my_work).start()
if __name__ == '__main__':
app = QApplication(sys.argv)
g = Global()
g.MainWindow.show()
g.MainWindow.thread_main()
g.MainWindow.thread_no_wait()
app.exec_()
how to run a QMessageBox from another process?
MyClass.work performed in a separate thread without join is necessary to MyClass suspended if it causes QMessageBox If we define QMessageBox is the MainWindow , the error will be called
QObject :: startTimer: timers can not be started from another thread
QApplication: Object event filter can not be in a different thread.
And in this call QMessageBox invoked in a separate thread , and the work is not suspended
It sounds like you want to pause execution of a thread until certain operations in the main thread complete. There are a few ways you can do this. A simple way is to use a shared global variable.
PAUSE = False
class MyClass:
def work(self):
global PAUSE
for r in range(100):
while PAUSE:
time.sleep(1)
if r == 2:
self.global_.MainWindow.SignalBox.emit('MyClass NO PAUSE')
PAUSE = True
...
class MainWindow(...)
def msgBox(self, txt):
QtGui.QMessageBox.information(self, txt, txt)
global PAUSE
PAUSE = False
If you use QThreads instead of python threads, you can actually call functions in another thread and block until that function completes using QMetaObject.invokeMethod
#QtCore.pyqtSlot(str)
def msgBox(self, txt):
...
...
if r == 2:
# This call will block until msgBox() returns
QMetaObject.invokeMethod(
self.global_.MainWindow,
'msgBox',
QtCore.Qt.BlockingQueuedConnection,
QtCore.Q_ARG(str, 'Text to msgBox')
)
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 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.