I am using following code to close Qwidget window automatically after some period of time
class ErrorWindow2(QtGui.QWidget):
def __init__( self ):
QtGui.QWidget.__init__( self, None, QtCore.Qt.WindowStaysOnTopHint)
msgBox = QMessageBox(self)
msgBox.move (500,500)
msgBox.setIcon(QMessageBox.Critical)
msgBox.setText("Test 2")
msgBox.setWindowTitle("ERROR")
msgBox.setStandardButtons(QMessageBox.Ok)
self.errWin2Timer = QtCore.QTimer()
self.errWin2Timer.timeout.connect(self.closeBox)
self.errWin2Timer.setSingleShot(True)
self.errWin2Timer.start(10000)
ret = msgBox.exec_()
if ret == QtGui.QMessageBox.Ok:
return
else:
return
def closeBox(self):
self.close()
def closeEvent(self, event):
logger.debug("Reached Error window 1 close event")
if self.errWin2:
self.errWin2.stop()
self.errWin2.deleteLater()
event.accept()
But the problem is that self.close doesn't work. What is the best possible way to close the window automatically after some period of time?
The problem is that when you put ret = msgBox.exec_() before the constructor finishes executing, the window object has not finished being built, so there is nothing to close, so when the dialog is closed the window that was just opened will be displayed. I finish building. The idea is to finish building the window and then call ret = msgBox.exec_() and for that we will use a QTimer.singleShot().
On the other hand, the closeEvent method is not necessary since I was trying to do it. IMHO is to eliminate the self.errWin2Timer from memory (although it seems that there was a typo since you use errWin2 instead of errWin2Timer) but being son of the window is not necessary since in Qt if the parent dies the children will also die.
from PyQt4 import QtCore,QtGui
class ErrorWindow2(QtGui.QWidget):
def __init__( self ):
super(ErrorWindow2, self).__init__(None, QtCore.Qt.WindowStaysOnTopHint)
self.errWin2Timer = QtCore.QTimer(self,
interval=10*1000,
singleShot=True,
timeout=self.close)
self.errWin2Timer.start()
QtCore.QTimer.singleShot(0, self.showMessageBox)
def showMessageBox(self):
msgBox = QtGui.QMessageBox(self)
msgBox.move (500,500)
msgBox.setIcon(QtGui.QMessageBox.Critical)
msgBox.setText("Test 2")
ret = msgBox.exec_()
if ret == QtGui.QMessageBox.Ok:
print("OK")
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
w = ErrorWindow2()
w.show()
sys.exit(app.exec_())
I have an installer I am creating for a game and as of now there are two buttons. One downloads the game, and one starts the game if it detects the executable. I multi-threaded both buttons so that my GUI will not freeze when I click either button. The problem is, if I click one of the buttons, the other will not work until restarting the application. I need some way for the thread to close after its process is completed so that the thread is open for the other button to work.
Here is what I have so far:
# Import Libraries
import requests, os, sys, zipfile, shutil, subprocess, wx, urllib, time
from threading import *
# Define global variables
url = "{ENTER DROPBOX URL HERE}" # The url to the file we are downloading
myEVT_PROGRESS = wx.NewEventType() # Custom Event Type
EVT_PROGRESS = wx.PyEventBinder(myEVT_PROGRESS, 1) # Bind specific events to event handlers
ID_START = wx.NewId()# Button definitions
EVT_RESULT_ID = wx.NewId()# Define notification event for thread completion
# Version Check
def VersionCheck():
try:
CurrentVersion = os.listdir("./RFMB6_WINDOWS/")[0] # Checks the version currently downloaded
VersionCheck = requests.get('https://pastebin.com/raw/yc30uwAh') # Checks the newest version
NewestVersion = VersionCheck.text # Converts VersionCheck to a string
if CurrentVersion == NewestVersion:
message = 'It looks like you have the newest version already.\n Are you sure you want to download?'
wx.MessageBox(message=message, caption='RFMP GUIntaller | Complete!', style=wx.OK | wx.ICON_INFORMATION)
else:
print('\n\nThere is an update available, would you like to install it?')
pass
except:
print("It looks like you don't have RFMP installed yet. Let me fix that for you.")
# Downloads new file
def Download():
urllib.request.urlretrieve(url, 'RFMP.zip')
# Extracts new file
def Extract():
zip_ref = zipfile.ZipFile("RFMP.zip", 'r')
zip_ref.extractall("RFMB6_WINDOWS")
zip_ref.close()
# Deletes the .zip file but leave the folder
def Clean():
os.remove("RFMP.zip")
class ProgressEvent(wx.PyCommandEvent):
"""Event to signal that a status or progress changed"""
def __init__(self, etype, eid, status=None, progress=None):
"""Creates the event object"""
wx.PyCommandEvent.__init__(self, etype, eid)
self._status = status # field to update label
self._progress = progress # field to update progress bar
def GetValue(self):
"""Returns the value from the event.
#return: the tuple of status and progress
"""
return (self._status, self._progress)
# Thread class that executes processing
class DLThread(Thread):
"""Worker Thread Class."""
def __init__(self, notify_window):
"""Init Worker Thread Class."""
Thread.__init__(self)
self._notify_window = notify_window
self.start()
# This is what runs on a separate thread when you click the download button
def run(self):
# This is the code executing in the new thread.
self.sendEvent('Checking for old files...', 00)
self.sendEvent('Checking for old files...', 100)
time.sleep(.5)
if os.path.exists("RFMB6_WINDOWS"):
self.sendEvent('Removing old files...', 200)
subprocess.check_call(('attrib -R ' + 'RFMB6_WINDOWS' + '\\* /S').split())
shutil.rmtree('RFMB6_WINDOWS')
time.sleep(.3)
self.sendEvent('Removed old files.', 300)
else:
time.sleep(.3)
self.sendEvent('No old files found.', 300)
time.sleep(.3)
pass
self.sendEvent('Downloading Package...', 400)
Download()
self.sendEvent('Downloading complete.', 600)
time.sleep(.3)
self.sendEvent('Extracting...', 650)
Extract()
self.sendEvent('Extraction complete.', 900)
time.sleep(.3)
self.sendEvent('Cleaning up...', 950)
Clean()
time.sleep(.3)
self.sendEvent('Cleaning complete.', 1000)
time.sleep(.5)
done = ("Installation the RFMP Private Alpha has been completed!")
wx.MessageBox(message=done, caption='RFMP GUIntaller | Complete!', style=wx.OK | wx.ICON_INFORMATION)
self._notify_window.worker = None
def sendEvent(self, status=None, progress=None):
# Send event to main frame, first param (str) is for label, second (int) for the progress bar
evt = ProgressEvent(myEVT_PROGRESS, -1, status, progress)
wx.PostEvent(self._notify_window, evt)
class StartAppThread(Thread):
"""Worker Thread Class."""
def __init__(self, notify_window):
"""Init Worker Thread Class."""
Thread.__init__(self)
self._notify_window = notify_window
# This starts the thread running on creation.
self.start()
# This is what runs on a separate thread when you click the download button
def run(self):
try:
subprocess.run('RFMB6_WINDOWS/RFMB6_WINDOWS/RFMB6.exe')
except:
error = ("Failed to locate RFMB6.exe. Please don't move any game files after downloading.")
wx.MessageBox(message=error, caption='RFMP GUIntaller | Error!',
style=wx.OK | wx.ICON_ERROR)
self._notify_window.worker = None
# GUI Frame class that spins off the worker thread
class MainFrame(wx.Frame):
"""Class MainFrame."""
def __init__(self, parent, id):
"""Create the MainFrame."""
wx.Frame.__init__(self, parent, id, 'RFMP GUInstaller',
style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER
^ wx.MAXIMIZE_BOX)
self.SetSize(400, 350)
self.Centre()
DLStart = wx.Button(self.bitmap1, ID_START, 'Download RFMP', size=(175,50), pos=(50,260))
DLStart.Bind(wx.EVT_BUTTON, self.OnButton_DLStart)
AppStart = wx.Button(self.bitmap1, ID_START, 'Start RFMP', size=(175,50), pos=(50,160))
AppStart.Bind(wx.EVT_BUTTON, self.OnButton_AppStart)
self.status = wx.StaticText(self.bitmap1, -1, '', pos=(10,215), style=wx.NO_BORDER)
self.status.SetBackgroundColour((255,255,0)) # set text back color
self.gauge = wx.Gauge(self.bitmap1, range = 1000, size = (375, 30), pos=(10,230),
style = wx.GA_HORIZONTAL)
# And indicate we don't have a worker thread yet
self.worker = None
self.Bind(EVT_PROGRESS, self.OnResult) # Bind the custom event to a function
def OnButton_DLStart(self, event):
# Trigger the worker thread unless it's already busy
VersionCheck()
if not self.worker:
self.worker = DLThread(self)
def OnButton_AppStart(self, event):
if not self.worker:
self.worker = StartAppThread(self)
def OnResult(self, event):
"""Our handler for our custom progress event."""
status, progress = event.GetValue()
self.status.SetLabel(status)
if progress:
self.gauge.SetValue(progress)
class MainApp(wx.App):
"""Class Main App."""
def OnInit(self):
"""Init Main App."""
self.frame = MainFrame(None, -1)
self.frame.Show(True)
self.SetTopWindow(self.frame)
return True
# Main Loop
if __name__ == '__main__':
app = MainApp(0)
app.MainLoop()
Your issue is caused by the fact that self.worker has a value.
You need to reset self.worker.
Below I have adjusted your code to do that and in doing so I have renamed notify_window to parent, simply because it makes what is going on more obvious and fits with python standards. I'm sure that there are many others ways of achieving this, this is just a simplistic way of achieving it, in this case.
import requests, os, sys, zipfile, shutil, subprocess, wx, urllib, time
from threading import *
class DLThread(Thread):
"""Worker Thread Class."""
def __init__(self, parent):
"""Init Worker Thread Class."""
Thread.__init__(self)
self.parent = parent
self.stop_download = 0
self.setDaemon(1)
self.start()
def run(self):
# This is the code executing in the new thread.
'''
This is what runs on a separate thread when you click the download button
'''
x = 0
while self.stop_download == 0:
time.sleep(0.5)
x +=1
if x > 20:
self.stop_download = 1
print ("Downloading App", x)
print("Download finished")
self.parent.worker = None
def stop(self):
self.stop_download = 1
print ("Download Cancelled")
class StartAppThread(Thread):
"""Worker Thread Class."""
def __init__(self, parent):
"""Init Worker Thread Class."""
Thread.__init__(self)
self.parent = parent
self.stop_app_thread = 0
self.setDaemon(1)
self.start()
def run(self):
# This is the code executing in the new thread.
'''
This is what runs on a separate thread when you click the Start App button.
'''
x= 0
while self.stop_app_thread == 0:
print ("Game in progress",str(x))
time.sleep(0.5)
x +=1
print ("Game finished")
self.parent.worker = None
def stop(self):
self.stop_app_thread = 1
# GUI Frame class that spins off the worker thread
class MainFrame(wx.Frame):
"""Class MainFrame."""
#Main Window
def __init__(self, parent, id):
"""Create the MainFrame."""
wx.Frame.__init__(self, parent, id, 'RFMP GUInstaller',
style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER
^ wx.MAXIMIZE_BOX)
self.SetSize(400, 350)
#self.bitmap1 = wx.StaticBitmap(self)
self.bitmap1 = wx.Panel(self)
self.Centre()
# Variables
myEVT_PROGRESS = wx.NewEventType() # Custom Event Type
EVT_PROGRESS = wx.PyEventBinder(myEVT_PROGRESS, 1) # Bind specific events to event handlers
ID_START = wx.NewId()# Button definitions
EVT_RESULT_ID = wx.NewId()# Define notification event for thread completion
# Download button
DLStart = wx.Button(self.bitmap1, ID_START, 'Download', size=(175,50), pos=(50,260))
DLStart.Bind(wx.EVT_BUTTON, self.OnButton_DLStart)
# App Start button
AppStart = wx.Button(self.bitmap1, ID_START, 'Start App', size=(75,50), pos=(50,160))
AppStart.Bind(wx.EVT_BUTTON, self.OnButton_AppStart)
# App Stop button
AppStop = wx.Button(self.bitmap1, ID_START, 'Stop', size=(75,50), pos=(150,160))
AppStop.Bind(wx.EVT_BUTTON, self.OnButton_AppStop)
# Progress bar
self.gauge = wx.Gauge(self.bitmap1, range = 1000, size = (375, 30), pos=(10,230), style = wx.GA_HORIZONTAL)
# And indicate we don't have a worker thread yet
self.worker = None
self.Bind(EVT_PROGRESS, self.OnResult) # Bind the custom event to a function
def OnButton_DLStart(self, event):
# Trigger the worker thread unless it's already busy
if not self.worker:
self.worker = DLThread(self)
def OnButton_AppStart(self, event):
if not self.worker:
self.worker = StartAppThread(self)
def OnButton_AppStop(self, event):
if self.worker:
self.worker.stop()
print ("App Stop command")
def OnResult(self, event):
"""Our handler for our custom progress event."""
status, progress = event.GetValue()
self.status.SetLabel(status)
if progress:
self.gauge.SetValue(progress)
class MainApp(wx.App):
"""Class Main App."""
def OnInit(self):
"""Init Main App."""
self.frame = MainFrame(None, -1)
self.frame.Show(True)
self.SetTopWindow(self.frame)
return True
# Main Loop
if __name__ == '__main__':
app = MainApp(0)
app.MainLoop()
I did a lot of research, but I didn't find a good solution.
I want to create a Gui that has a small area where I can launch an external program and show its output in real time in a text area. This program is console application. In addition, I must have the possibility to stop it in any time (start/stop/re-start).
An example with a thread that outputs the second window and outputs the result from the stream in the main window.
Comments in the text of the program:
import random
from PyQt5 import Qt
class WorkThread(Qt.QThread):
''' Streaming task in its window.
Signals and Slots are used for communication between objects. '''
# Declare a signal, with an argument (int) for transmission in the connected slots
threadSignal = Qt.pyqtSignal(int)
def __init__(self, startParm):
super().__init__()
self.startParm = startParm # Initialize the parameters passed to the task
def run(self, *args, **kwargs):
c = self.startParm
while True:
Qt.QThread.msleep(200)
c += 1
self.threadSignal.emit(c) # We disable the signal and pass arguments to the connected slot
class WorkThreadMain(Qt.QThread):
''' Streaming Main task '''
threadSignalMain = Qt.pyqtSignal(int)
def __init__(self, startParm):
super().__init__()
self.startParm = startParm
def run(self, *args, **kwargs):
c = self.startParm
while True:
Qt.QThread.msleep(1000)
c += 1
self.threadSignalMain.emit(c)
class MsgBox(Qt.QDialog):
""" Window initialization class for visualizing an additional stream
and a button to close the stream window if the thread is stopped! """
def __init__(self):
super().__init__()
layout = Qt.QVBoxLayout(self)
self.label = Qt.QLabel("")
layout.addWidget(self.label)
close_btn = Qt.QPushButton("Close thread")
layout.addWidget(close_btn)
close_btn.clicked.connect(self.close)
self.setGeometry(900, 65, 400, 80)
self.setWindowTitle('MsgBox for WorkThread')
class MainWindow(Qt.QWidget):
''' Main Window '''
def __init__(self):
super().__init__()
layout = Qt.QVBoxLayout(self)
self.labelMain = Qt.QLabel("The result of the Main task: ")
layout.addWidget(self.labelMain)
self.labelThread = Qt.QLabel("The result of the Thread task: ")
layout.addWidget(self.labelThread)
validator = Qt.QIntValidator(1, 999, self)
validator.setBottom(1)
self.lineEdit = Qt.QLineEdit()
self.lineEdit.setPlaceholderText("Enter the initial parameter for the stream task")
self.lineEdit.setValidator(validator) # self.lineEdit will only take integers from 1 to 999
layout.addWidget(self.lineEdit)
self.btn = Qt.QPushButton("Start thread!")
layout.addWidget(self.btn)
self.btnMain = Qt.QPushButton("Start Main!")
layout.addWidget(self.btnMain)
self.setGeometry(550, 65, 300, 200)
self.setWindowTitle('MainWindow')
self.btn.clicked.connect(self.on_btn)
self.btnMain.clicked.connect(self.on_btnMain)
self.msg = MsgBox()
self.thread = None
self.threadMain = None
def on_btn(self):
''' Starting or Stopping an Additional Stream-WorkThread from the main window '''
# Input parameters for transfer to the stream, if not specified, we pass default `0`
startParm = int(self.lineEdit.text()) if self.lineEdit.text()!="" else 0
if self.thread is None:
self.thread = WorkThread(startParm)
self.thread.threadSignal.connect(self.on_threadSignal)
self.thread.start()
self.btn.setText("Stop thread")
self.lineEdit.hide()
else:
self.thread.terminate()
self.thread = None
self.btn.setText("Start thread")
self.lineEdit.show()
def on_threadSignal(self, value):
''' Visualization of streaming data-WorkThread. '''
self.msg.label.setText(str(value))
self.labelThread.setText("The result of the Thread task: " + str(value)) # We show also in the main window
# We restore the rendering of the stream window if it was closed. The flow is working.
if not self.msg.isVisible():
self.msg.show()
def on_btnMain(self):
''' Starting or Stopping the Main Thread-WorkThreadMain '''
cM = random.randrange(1, 100)
if self.threadMain is None:
self.threadMain = WorkThreadMain(cM)
self.threadMain.threadSignalMain.connect(self.on_threadSignalMain)
self.threadMain.start()
self.btnMain.setText("Stop Main")
else:
self.threadMain.terminate()
self.threadMain = None
self.btnMain.setText("Start Main")
def on_threadSignalMain(self, value):
''' Visualization of streaming data WorkThreadMain '''
self.labelMain.setText("The result of the Main task: " + str(value))
if __name__ == '__main__':
app = Qt.QApplication([])
mw = MainWindow()
mw.show()
app.exec()
I used self.close to close the widget that is running. Whole code is as follows:
class timeTracker(QWidget):
'''time tracker main application v_0.8'''
def __init__(self, parent=None):
super(timeTracker, self).__init__(parent)
self.grid = QGridLayout()
self.setLayout(self.grid)
while not self.connectivity():
print(self.connectivity())
ret = QMessageBox.warning(self, "Not connected to network. Please check the connection", "", QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
if ret == QMessageBox.No:
self.close()
qApp.quit()
break
qApp.quit()
# self.time_widget = timeline.timeWidget()
# self.grid.addWidget(self.time_widget, 0, 0)
def connectivity(self):
try:
urllib.request.urlopen("http://www.google.com", timeout=1)
return True
except urllib.error.URLError as error:
return False
if __name__ == "__main__":
application = QApplication(sys.argv)
main_widget = timeTracker()
main_widget.show()
main_widget.move(10, 10)
sys.exit(application.exec_())
What I expect from this code is, as long as the connectivity function is returning True, user will be repeatedly asked to do the certain action if they click yes. If they click no, I would like to terminate the whole widget. But when I click no, then it just goes through the lines after the self.close. What can I do to actually terminate the program?
To close an application forcefully as in this case we can use sys.exit()
if ret == QMessageBox.No:
sys.exit(0)
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.