here I have a simple GUI. With the creation of this GUI I am starting a thread (THREAD1) which is running while the GUI is running. The task of this thread (THREAD1) is, to perform a specific action in a specific interval.
It is important, that this specific action is performed as fast as possible. So I am creating new thread objects. This is accomplished by THREAD1
Until here everything is working fine. I get the THREAD1 to work. I am also able to get threads created by THREAD1 to work.
here the code.
from PyQt6.QtCore import QThread, QObject
from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget
import time
class GetMarketDataV2(QThread):
def __init__(self, index):
super(GetMarketDataV2, self).__init__()
self.index = index
def run(self):
time.sleep(5)
print(f"Thread: {self.index}\n")
class DataCollectionLoop(QObject):
"""
Runs as long as the GUI is running. Terminated with the Main window close event.
The task of this class is, to start a specific amount of threads with a specific interval.
"""
def __init__(self):
super(DataCollectionLoop, self).__init__()
self.thread = {}
self.first_startup = True
def run(self):
while True:
# In this example 10 threads are created.
for i in range(10):
self.thread[i] = GetMarketDataV2(index=i)
self.thread[i].start()
# I want this line below to execute after all threads above are done with their job.
print("print this statement after all threads are finished")
# Here the specific interval is 10 seconds.
time.sleep(10)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
# Setting up thread which controls the data collection.
self.thread = QThread()
# Instantiating the DataCollectionLoop object and moving it to a thread.
data_collection = DataCollectionLoop()
data_collection.moveToThread(self.thread)
# calling the run method when the thread is started.
self.thread.started.connect(data_collection.run)
# Starting the thread
self.thread.start()
# Minimal GUI
self.centralWidget = QWidget()
self.setCentralWidget(self.centralWidget)
self.show()
# Terminating the thread when window is closed.
def closeEvent(self, event):
self.thread.terminate()
app = QApplication([])
window = MainWindow()
I have searched a decent amount of time, but couldn't find a solution to my problem.
Which is:
I want to wait for all threads, which are created by THREAD1, to finish, before continuing with my code.
I think I should catch the states of each thread (if they are finished or not) but I don't know exactly how..
Thank you very much!
As #musicamante suggested I got the desired result using the isFinished() method.
Here is how I made it:
# Waits for all threads to finnish. Breaks out of the loop afterwards
while True:
for thread in self.thread.values():
if not thread.isFinished():
break
else:
break
Another solution I stumpled uppon which does the trick for me, is to work with QRunnables and a Threadpool.
for market in forex_markets:
market = GetMarketData() # Subclassed QRunnable
self.threadpool.start(market)
self.threadpool.waitForDone()
Thank you!
Related
After read and searching I am trying to use the generate a QObject then use the movetoThread method to run an independent process and allow the QMainWindow to continue to respond. This has not worked when I have tried to implement the operation in a QThread.run() method. The following code is my attempt to make a simple example. While the code works in running thread independent of the MainWindow, it does not abort. The only way I can get a thread to stop is to set worker.end = True. Which I think should not be the way to do it.
"""
This is a program to test Threading with Objects in PyQt4.
"""
from time import sleep
import sys
from PyQt4.QtCore import QObject, pyqtSlot, pyqtSignal, QThread
from PyQt4.QtGui import QMainWindow, QApplication, QProgressBar
from PyQt4.QtGui import QPushButton, QVBoxLayout, QWidget
class workerObject(QObject):
bar_signal = pyqtSignal(int)
res_signal = pyqtSignal(str)
term_signal = pyqtSignal()
def __init__(self, maxIters):
super(workerObject, self).__init__()
self.maxIters = maxIters
def run(self):
self.bar_signal.emit(self.maxIters)
sleep(1)
self.end = False
for step in range(self.maxIters):
if self.end:
self.maxIters = step
break
self.bar_signal.emit(step)
sleep(2)
self.res_signal.emit("Got to {}".format(self.maxIters))
self.term_signal.emit()
#pyqtSlot()
def mystop(self):
print "stop signalled?"
self.end = True
class MCwindow(QMainWindow):
abort_signal = pyqtSignal(name='abort_signal')
def __init__(self):
super(MCwindow,self).__init__()
self.maxIters = 50
widget = QWidget()
layout = QVBoxLayout(widget)
self.go_btn = QPushButton()
self.go_btn.setText('Go')
layout.addWidget(self.go_btn)
self.abort_btn = QPushButton()
self.abort_btn.setText('Stop')
layout.addWidget(self.abort_btn)
self.simulation_bar = QProgressBar()
self.simulation_bar.setRange(0, self.maxIters)
self.simulation_bar.setFormat("%v")
layout.addWidget(self.simulation_bar)
self.setCentralWidget(widget)
self.go_btn.clicked.connect(self.run_mc)
# The button calls the windows method to stop --- it could
# be that is 'clicked' calls the worker.mystop
# self.abort_btn.clicked.connect(self.stop_mc)
# This allows for the abort button to do somethign in the MainWindow
# before the abort_signal is sent, this works
self.abort_btn.clicked.connect(self.stop_mc)
def run_mc(self):
self.thread = QThread()
self.worker = workerObject(self.maxIters)
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
# This is the simple stop method, but does not work
# self.abort_btn.clicked.connect(self.worker.mystop)
# This uses the signal in the MCwindow - this connection does NOT works
self.abort_signal.connect(self.worker.mystop)
# This does NOT stop the thread
# and would not allow for any clean up in the worker.
# self.abort_signal.connect(self.thread.terminate)
# This is a 'bad' way to stop the woker ... It does, however, work
# self.abort_signal.connect(self.stopper)
self.worker.bar_signal.connect(self.setBar)
self.worker.res_signal.connect(self.setData)
self.worker.term_signal.connect(self.thread.terminate)
self.thread.start()
def stop_mc(self):
print "Stopping?!"
# This signal is NEVER seen by the Worker.
self.abort_signal.emit()
def stopper(self):
print "I should stop?!"
# Should use signals to tell the worker to stop - and not setting a attribute
self.worker.end=True
#pyqtSlot(int)
def setBar(self, val):
self.simulation_bar.setValue(val)
#pyqtSlot(str)
def setData(self, txt):
print "Got done Sig!", txt
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MCwindow()
window.show()
sys.exit(app.exec_())
The reason why the slot connected to abort_signal doesn't seem to get called, is because cross-thread signals are queued by default. This means the signal will be wrapped as an event and posted to the event queue of whichever thread the receiver is living in.
In your particular example, the receiver is a worker object which has been moved to a worker thread. Calling start() on the worker thread will start its event-loop, and that is where abort_signal will be queued. However, the run() method of the worker object starts a for loop, which will block the thread's event processing in exactly the same way it would if it was executed in the main gui thread!
You can more clearly see what's happening if you make a few adjustments to your example:
class MCwindow(QMainWindow):
abort_signal = pyqtSignal(name='abort_signal')
def __init__(self):
super(MCwindow,self).__init__()
# use a sane default
self.maxIters = 5
...
# DO NOT use QThread.terminate
self.worker.term_signal.connect(self.thread.quit)
Now run the example, and then click the Go button, click the Stop button, and wait for the worker to complete normally. This should produce output like this:
Stopping?!
Got done Sig! Got to 5
stop signalled?
Note that "stop signalled" is output last - i.e. after run() exits and control has returned to the thread's event-loop. In order to process in-coming signals while the worker is running, you will need to force immediate processing of the thread's pending events. This can be done like this:
for step in range(self.maxIters):
QApplication.processEvents()
...
With that in place, you should then see output like this:
Stopping?!
stop signalled?
Got done Sig! Got to 2
Which is presumably what you intended.
Typically a thread will close when it exits the run method. The other way to get a regular python thread to close is by calling it's join method.
For PyQt the join method should either be the quit or terminate method. You should probably still set your end variable to True.
I am making window application in PyQt5 and I want to parse data from XML file in the backgrond and send it to second class. I want to use threads with queue to handle between them. When I want to display window app i see black window. It would be nice to use python threads but i tried to do it on QThread and it is not working too idk why...
This is code example
import queue
import sys
import threading
from PyQt5.QtCore import QThread, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import *
class Test_generator:
def __init__(self, queue_obj):
super().__init__()
self.queue_obj = queue_obj
#self.parser_thread = threading.Thread(target=self.parser())
def sender(self):
for _ in range(100):
string ="test"
self.queue_obj.put(string)# send to queue
time.sleep(1)
#print(string)
class Test_collectioner:
def __init__(self,queue_obj):
self.queue_obj = queue_obj
def get_data(self):
collection = []
while self.queue_obj.empty() is False:
print("xd")
print(self.queue_obj.get(), "xd")
#I found this example in the internet(Not working)
class Threaded(QThread):
result = pyqtSignal(int)
def __init__(self, parent=None, **kwargs):
super().__init__(parent, **kwargs)
#pyqtSlot(int)
def run(self):
while True:
print("test")
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.GUI()
self.setWindowTitle("PyQt5 app")
global q
q = queue.Queue()
# BLACKSCREEN (for Test_generator)
test_generator_obj = Test_generator(q)
test_collectioner_obj = Test_collectioner(q)
t1 = threading.Thread(target=test_generator_obj.sender())
t2 = threading.Thread(target=test_collectioner_obj.get_data())
t1.start()
t2.start()
# BLACKSCREEN TOO
"""self.thread = QThread()
self.threaded = Threaded()
self.thread.started.connect(self.threaded.run())
self.threaded.moveToThread(self.thread)
qApp.aboutToQuit.connect(self.thread.quit)
self.thread.start()"""
def GUI(self):
self.showMaximized()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())
There are two main problems in your code:
you're executing the methods used for the thread, but you should use the callable object instead; you did the same mistake for the QThread, since connections to signals also expects a callable, but you're actually executing the run function from the main thread (completely blocking everything), since you're using the parentheses; those lines should be like this:
t1 = threading.Thread(target=test_generator_obj.sender)
t2 = threading.Thread(target=test_collectioner_obj.get_data)
or, for the QThread:
self.thread.started.connect(self.threaded.run)
due to the python GIL, multithreading only releases control to the other threads whenever it allows to, but the while cycle you're using prevents that; adding a small sleep function ensures that control is periodically returned to the main thread;
Other problems also exist:
you're already using a subclass of QThread, so there's no use in using another QThread and move your subclass to it;
even assuming that an object is moved to another thread, the slot should not be decorated with arguments, since the started signal of a QThread doesn't have any; also consider that slot decorators are rarely required;
the thread quit() only stops the event loop of the thread, but if run is overridden no event loop is actually started; if you want to stop a thread with a run implementation, a running flag should be used instead; in any other situation, quitting the application is normally enough to stop everything;
Note that if you want to interact with the UI, you can only use QThread and custom signals, and basic python threading doesn't provide a suitable mechanism.
class Threaded(QThread):
result = pyqtSignal(int)
def __init__(self, parent=None, **kwargs):
super().__init__(parent, **kwargs)
def run(self):
self.keepGoing = True
while self.keepGoing:
print("test")
self.msleep(1)
def stop(self):
self.keepGoing = False
class MainWindow(QMainWindow):
def __init__(self):
# ...
self.threaded = Threaded()
self.threaded.start()
qApp.aboutToQuit.connect(self.threaded.stop)
I would like to run a progress bar in a different thread from the rest of my code, but I would like to control how the progress bar updates from my main thread.
Is this something which is possible?
This is what I have so far:
import time
from PySide import QtGui
from PySide import QtCore
from PySide import QtUiTools
class progressBar(QtGui.QDialog, QtCore.QThread):
def __init__(self, window, title=None):
super(progressBar, self).__init__(window)
QtCore.QThread.__init__(self)
self.title = title or 'Progress'
self.setupUi()
self.show()
def setupUi(self):
self.setObjectName("Thinking")
self.gridLayout = QtGui.QGridLayout(self)
self.gridLayout.setObjectName("gridLayout")
self.progressBar = QtGui.QProgressBar(self)
self.gridLayout.addWidget(self.progressBar, 0, 0, 1, 1)
# ADJUSTMENTS
self.setMaximumSize(280, 50)
self.setMinimumSize(280, 50)
self.setWindowTitle(self.title)
def increase(self, inc):
self.progressBar.setProperty("value", inc)
time.sleep(0.01)
def run(self):
for i in range(1,101):
self.increase(i)
progressThread = progressBar(QtGui.QApplication.activeWindow())
progressThread.start()
This seems to be running the progress bar correctly inside of the thread, but it is controlled completely by the run function.
I tried removing the run function and adding this code to my main thread:
progressThread = progressBar(QtGui.QApplication.activeWindow())
progressThread.start()
for i in range(1,101):
progressThread.increase(i)
But this didn't seem to work.
Any help with this would be great... Thanks
I believe bnaecker, Brendan Abel and Steve Cohen already gave you the key bits of info in their comments.
As they said already, you can definitely run your progress bar and your logic in separate threads, provided the UI run on the main thread.
Here is an example which should work the way you want:
import time, random
import threading
from PySide import QtCore, QtGui
class ProgressWidget(QtGui.QWidget):
# just for the purpose of this example,
# define a fixed number of threads to run
nthreads = 6
def __init__(self):
super(ProgressWidget, self).__init__()
self.threads = []
self.workers = []
self.works = [0 for i in range(self.nthreads)]
self.setupUi()
self.setupWorkers()
self.runThreads()
def drawProgessBar(self):
self.progressBar = QtGui.QProgressBar(self)
self.progressBar.setGeometry(QtCore.QRect(20, 20, 582, 24))
self.progressBar.minimum = 1
self.progressBar.maximum = 100
self.progressBar.setValue(0)
def setupUi(self):
self.setWindowTitle("Threaded Progress")
self.resize(600, 60)
self.drawProgessBar()
def buildWorker(self, index):
"""a generic function to build multiple workers;
workers will run on separate threads and emit signals
to the ProgressWidget, which lives in the main thread
"""
thread = QtCore.QThread()
worker = Worker(index)
worker.updateProgress.connect(self.handleProgress)
worker.moveToThread(thread)
thread.started.connect(worker.work)
worker.finished.connect(thread.quit)
QtCore.QMetaObject.connectSlotsByName(self)
# retain a reference in the main thread
self.threads.append(thread)
self.workers.append(worker)
def setupWorkers(self):
for i in range(self.nthreads):
self.buildWorker(i)
def runThreads(self):
for thread in self.threads:
thread.start()
def handleProgress(self, signal):
"""you can add any logic you want here,
it will be executed in the main thread
"""
index, progress = signal
self.works[index] = progress
value = 0
for work in self.works:
value += work
value /= float(self.nthreads)
# management of special cases
if value >= 100:
self.progressBar.hide()
return
# else
self.progressBar.setValue(value)
print 'progress (ui) thread: %s (value: %d)' % (threading.current_thread().name, value)
class Worker(QtCore.QObject):
"""the worker for a threaded process;
(this is created in the main thread and
then moved to a QThread, before starting it)
"""
updateProgress = QtCore.Signal(tuple)
finished = QtCore.Signal(int)
def __init__(self, index):
super(Worker, self).__init__()
# store the Worker index (for thread tracking
# and to compute the overall progress)
self.id = index
def work(self):
for i in range(100):
print 'worker thread: %s' % (threading.current_thread().name, )
# simulate some processing time
time.sleep(random.random() * .2)
# emit progress signal
self.updateProgress.emit((self.id, i + 1))
# emit finish signal
self.finished.emit(1)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
ui = ProgressWidget()
ui.show()
sys.exit(app.exec_())
Here's a minimal breakdown:
QThreads live in the main thread, where they're created (not in the thread that they manage)
in order to run tasks on separate threads, they must be passed to QThreads using the moveToThread method (workers need to subclass QObject for that method to be available)
a worker sends signals to update the progress bar and to notify they're done with their task
in this example we're running multiple tasks, each in its own thread. Progress signals are sent back to the main thread, where the logic to update the progress bar runs
A side note: in the console, the workers' output refers to "dummy" threads. This seems to be related to the fact that the threading module has no knowledge of QThreads (that's what I got from here, at least).
Still, it seems enough to prove that workers' tasks are running on separate threads. If anyone's got more accurate info, feel free to expand.
For those want to read more on this topic, here is a link which many articles refer to.
I am building a GUI with a socket connection running in a backround thread in PyQt5. Everything is working pretty well except the Qthread never emits the finished signal. Maybe this isn't a problem and I could change my implementation around to work around it, but is the expected behavior that the Qthread would continue to run once the object that has been moved has stopped doing anything?
Should I write a function in the main class to stop the thread once I'm done with it, or can I just move new things to that thread with no consequences?
class MyClass(PyQt5.QtWidgets.QMainWindow)
def __init__(self, parent=None):
# Setup thread for the socket control
self.simulThread = PyQt5.QtCore.QThread()
self.socketController = SocketController()
self.socketController.moveToThread(self.simulThread)
self.simulThread.started.connect(self.socketController.controlSocket)
self.simulThread.finished.connect(self.deadThread)
# Bind controls to events
self.ui.buttonConnect.clicked.connect(self.simulThread.start)
self.ui.buttonDisconnect.clicked.connect(lambda: self.socketController.stop())
def deadThread(self, data):
print ("THREAD IS DEAD.")
class SocketController(PyQt5.QtCore.QObject):
finished = PyQt5.QtCore.pyqtSignal()
def __init__(self):
super(SocketController, self).__init__()
self.run = True;
def controlSocket(self):
#setup the socket
while self.run:
# do socket stuff
time.sleep(1)
#close the socket
self.finished.emit()
def stop(self):
self.run = False;
A QThread has it's own event loop to handle signals. The controlSocket method blocks this event loop until self.run == False. But this never happens because self.run is only set to False when control is returned to the event lop and it can process the signal that runs the stop method.
You probably want to rearchitect your thread so that instead of having a while loop which blocks the QThread event loop, you construct a QTimer in the thread which calls the #do socket stuff code every 1 second. This way, control is returned to the threads event loop and it can process the stop signal (which would be changed to stop the QTimer from triggering again)
The answer is actually a combination of what three_pineapples and OP posted in their answers:
As noted by three_pineapples, the main problem is that controlSocket(), which gets called by the thread event loop when started signal is emitted, doesn't return until the stop() method gets called. However, in the OP's design, the stop() method can only be called by Qt if the socket thread can process events (since this is how cross-thread signals are dispatched, via the event loops). This is not possible while controlSocket() is busy looping and sleeping.
In order for the thread to exit, its event loop must be stopped.
The first issue must be fixed by either allowing the thread to process events during the loop, or by using a timer instead of a loop. Processing events is shown in this piece of code, based on the OP code:
import time
from PyQt5.QtWidgets import QMainWindow, QWidget, QPushButton, QApplication, QHBoxLayout
from PyQt5.QtCore import QThread, QObject, pyqtSignal
class MyClass(QWidget):
def __init__(self, parent=None):
super().__init__()
self.resize(250, 150)
self.setWindowTitle('Simple')
# Setup thread for the socket control
self.socketController = SocketController()
self.simulThread = QThread()
self.socketController.moveToThread(self.simulThread)
self.simulThread.started.connect(self.socketController.controlSocket)
self.simulThread.finished.connect(self.deadThread)
# Bind controls to events
self.buttonConnect = QPushButton('connect')
self.buttonConnect.clicked.connect(self.simulThread.start)
self.buttonDisconnect = QPushButton('disconnect')
self.buttonDisconnect.clicked.connect(self.socketController.stop)
hbox = QHBoxLayout()
hbox.addStretch(1)
hbox.addWidget(self.buttonConnect)
hbox.addWidget(self.buttonDisconnect)
self.setLayout(hbox)
def deadThread(self, data):
print("THREAD IS DEAD.")
class SocketController(QObject):
finished = pyqtSignal()
def __init__(self):
print('initialized')
super().__init__()
self.run = True
def controlSocket(self):
# setup the socket
print('control socket starting')
while self.run:
# do socket stuff
app.processEvents()
print('control socket iterating')
time.sleep(1)
# close the socket
self.finished.emit()
print('control socket done')
def stop(self):
print('stop pending')
self.run = False
app = QApplication([])
mw = MyClass()
mw.move(300, 300)
mw.show()
app.exec()
The QTimer is a little more complicated, but the idea is to have controlSocket() only do one iteration of the loop, and call it repeatedly via QTimer:
QTimer.singleShot(0, self.controlSocket)
and
def controlSocket(self):
# do socket stuff, then:
if self.run:
QTimer.singleShot(1000, self.controlSocket)
else:
self.finished.emit()
Either of the above approaches fixes the first problem, and allows the SocketController to stop doing its socket-related work.
The second problem is that even after the socket controller is done its job, the thread loop is still running (the two loops are independent). To make the event loop exit, it must be stopped via the thread's quit() method. The quit and the stop should be done together:
class MyClass(QWidget):
def __init__(self, parent=None):
...
self.buttonDisconnect = QPushButton('disconnect')
self.buttonDisconnect.clicked.connect(self.stop)
hbox = QHBoxLayout()
hbox.addStretch(1)
hbox.addWidget(self.buttonConnect)
hbox.addWidget(self.buttonDisconnect)
self.setLayout(hbox)
def stop(self):
self.simulThread.quit()
self.socketController.stop()
Ok, so I found A solution, but I don't really know if its correct. Instead of mapping the disconnect button to the socket stop function, I mapped it to function defined in MyClass that calls a new function I wrote called controlDisconnect which also tells the thread explicitly to quit.
# The changed line
self.ui.buttonDisconnect.clicked.connect(self.controlDisconnect)
def controlDisconnect(self):
self.socketController.stop()
self.simulThread.quit()
While this is working (the thread prints out the message that indicates it died), I'm not sure its really good practice. There should probably at least be some code to make sure the socketController has actually stopped before telling the thread to quit.
I have a GUI which needs to perform work that takes some time and I want to show the progress of this work, similar to the following:
import sys
import time
from PyQt4 import QtGui, QtCore
class MyProgress(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
# start loop with signal
self.button = QtGui.QPushButton('loop', self)
self.connect(self.button, QtCore.SIGNAL('clicked()'), self.loop)
# test button
self.test_button = QtGui.QPushButton('test')
self.connect(self.test_button, QtCore.SIGNAL('clicked()'), self.test)
self.pbar = QtGui.QProgressBar(self)
self.pbar.setMinimum(0)
self.pbar.setMaximum(100)
# layout
vbox = QtGui.QVBoxLayout()
vbox.addWidget(self.test_button)
vbox.addWidget(self.button)
vbox.addWidget(self.pbar)
self.setLayout(vbox)
self.show()
def update(self):
self.pbar.setValue(self.pbar.value() + 1)
def loop(self):
for step in range(100):
self.update()
print step
time.sleep(1)
def test(self):
if self.test_button.text() == 'test':
self.test_button.setText('ok')
else:
self.test_button.setText('test')
app = QtGui.QApplication(sys.argv)
view = MyProgress()
view.loop() # call loop directly to check whether view is displayed
sys.exit(app.exec_())
When I execute the code the loop method is called and it prints out the values as well as updates the progress bar. However the view widget will be blocked during the execution of loop and although this is fine for my application it doesn't look nice with Ubuntu. So I decided to move the work to a separate thread like this:
import sys
import time
from PyQt4 import QtGui, QtCore
class Worker(QtCore.QObject):
def __init__(self, parent=None):
QtCore.QObject.__init__(self, parent)
def loop(self):
for step in range(10):
print step
time.sleep(1)
class MyProgress(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
# test button
self.test_button = QtGui.QPushButton('test')
self.connect(self.test_button, QtCore.SIGNAL('clicked()'), self.test)
self.pbar = QtGui.QProgressBar(self)
self.pbar.setMinimum(0)
self.pbar.setMaximum(100)
# layout
vbox = QtGui.QVBoxLayout()
vbox.addWidget(self.test_button)
vbox.addWidget(self.pbar)
self.setLayout(vbox)
self.show()
def test(self):
if self.test_button.text() == 'test':
self.test_button.setText('ok')
else:
self.test_button.setText('test')
app = QtGui.QApplication(sys.argv)
view = MyProgress()
work = Worker()
thread = QtCore.QThread()
work.moveToThread(thread)
# app.connect(thread, QtCore.SIGNAL('started()'), work.loop) # alternative
thread.start()
work.loop() # not called if thread started() connected to loop
sys.exit(app.exec_())
When I run this version of the script the loop starts running (the steps are displayed in the terminal) but the view widget is not shown. This is the first thing I can't quite follow. Because the only difference from the previous version here is that the loop runs in a different object however the view widget is created before and therefore should be shown (as it was the case for the previous script).
However when I connected the signal started() from thread to the loop function of worker then loop is never executed although I start the thread (in this case I didn't call loop on worker). On the other hand view is shown which makes me think that it depends whether app.exec_() is called or not. However in the 1st version of the script where loop was called on view it showed the widget although it couldn't reach app.exec_().
Does anyone know what happens here and can explain how to execute loop (in a separate thread) without freezing view?
EDIT: If I add a thread.finished.connect(app.exit) the application exits immediately without executing loop. I checked out the 2nd version of this answer which is basically the same what I do. But in both cases it finishes the job immediately without executing the desired method and I can't really spot why.
The example doesn't work because communication between the worker thread and the GUI thread is all one way.
Cross-thread commnunication is usually done with signals, because it is an easy way to ensure that everything is done asynchronously and in a thread-safe manner. Qt does this by wrapping the signals as events, which means that an event-loop must be running for everything to work properly.
To fix your example, use the thread's started signal to tell the worker to start working, and then periodically emit a custom signal from the worker to tell the GUI to update the progress bar:
class Worker(QtCore.QObject):
valueChanged = QtCore.pyqtSignal(int)
def loop(self):
for step in range(0, 10):
print step
time.sleep(1)
self.valueChanged.emit((step + 1) * 10)
...
thread = QtCore.QThread()
work.moveToThread(thread)
thread.started.connect(work.loop)
work.valueChanged.connect(view.pbar.setValue)
thread.start()
sys.exit(app.exec_())