QThread Windows not responding - python

I wrote a GUI program in PyQt on Windows. There's some expensive operations in my program. While these operations are running, the program shows "Not Responding" in the program bar.
I think it must be this operation block the main thread to update the UI, so I write multi-threading code by QThread to test it, it still not make sense.
I wrote a small program to test it, the operation did not run in new thread at all, here is my small test code:
from PyQt5.QtCore import QThread, QObject, QCoreApplication, qDebug, QTimer
class Worker(QObject):
def on_timeout(self):
qDebug('Worker.on_timeout get called from: %s' % hex(int(QThread.currentThreadId())))
if __name__ == '__main__':
import sys
app = QCoreApplication(sys.argv)
qDebug('From main thread: %s' % hex(int(QThread.currentThreadId())))
t = QThread()
qDebug(t)
worker = Worker()
timer = QTimer()
timer.timeout.connect(worker.on_timeout)
timer.start(1000)
timer.moveToThread(t)
worker.moveToThread(t)
t.start()
app.exec_()
Here is the output:
From main thread: 0x634
Worker.on_timeout get called from: 0x634

Your program has several errors and does not produce the output that you show.
Firstly, it is not possible to pass a thread object to qDebug - the argument must be a string. If you want to print objects, use qDebug(repr(obj)) - or even better, just use print(obj).
Secondly, you cannot start a timer outside of the thread that created it. Your example makes the signal connection in the main thread, and starts the timer in the main thread as well. So worker.on_timeout will be called in the main thread. But if you connect and start the timer after moving it to the worker thread, you will get this error:
QObject::startTimer: Timers can only be used with threads started with
QThread
I think using a timer is unnecessary, and confuses your example, so it is better to leave it out altogether. Instead, you should connect the started signal of the worker thread to a run method of the worker object. To simulate a long-running operation, you can use QThread.sleep():
from PyQt5.QtCore import QThread, QObject, QCoreApplication
class Worker(QObject):
def run(self):
print('Worker called from: %#x' % int(QThread.currentThreadId()))
QThread.sleep(2)
print('Finished')
QCoreApplication.quit()
if __name__ == '__main__':
import sys
app = QCoreApplication(sys.argv)
print('From main thread: %#x' % int(QThread.currentThreadId()))
t = QThread()
worker = Worker()
worker.moveToThread(t)
t.started.connect(worker.run)
t.start()
app.exec_()
Finally, note that you should always make signal connections after moving a worker object to a thread. The reasons for this are explained in this answer.

Related

How to use a QTimer in a separate QThread

I have some computationally heavy task that I want to run in a loop after every 5 seconds without blocking the main event-loop. For this, I intend to use a QTimer and a separate thread to run it. I have tried the following code but it has not worked so far:
#pyqtSlot()
def heavy_task_function():
# Sleep for 10 seconds to simulate heavy computation
time.sleep(10)
print "First Timer Fired"
if __name__ == "__main__":
app = QCoreApplication.instance()
if app is None:
app = QApplication(sys.argv)
threaded_timer = ModbusComThread(heavy_task_function)
threaded_timer.start()
sys.exit(app.exec_())
Where:
class ModbusComThread(QThread):
def __init__(self, slot_function):
QThread.__init__(self)
self.slot_function = slot_function
self.send_data_timer = None
def run(self):
print "Timer started on different thread"
self.send_data_timer = QTimer(self)
self.send_data_timer.timeout.connect(self.slot_function)
self.send_data_timer.start(5000)
def stop(self):
self.send_data_timer.stop()
The slot_function is never fired by the QTimer in threaded_timer. Is my threading architecture correct?
A QTimer needs a running event-loop. By default, QThread.run() will start a local event-loop for the thread, but if you completely override it in the way that you have done, that won't happen - so the timer events will never be processed.
In general, when you need a local event-loop you should create a worker object to do all the processing and then use moveToThread to put it in a separate thread. If not, it's perfectly okay to override QThread.run().
The demo below shows how to do this. Note that it's very important to create the timer after the thread has started, otherwise it would be created in the wrong thread and its timer-events wouldn't be processed by the thread's event-loop. It's also important that all communication between the worker thread and the main thread is done via signals, so as to ensure thread-safety. Never try to directly perform GUI operations outside the main thread, as Qt does not support that at all. For the purposes of the demo, a second timer in the main thread is used to stop all processing after a fixed interval. If there was a GUI, user intervention via a button would achieve the same thing.
Demo:
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class ModbusComWorker(QObject):
finished = pyqtSignal()
def start(self):
self._timer = QTimer(self)
self._timer.timeout.connect(self.process)
self._timer.start(2000)
def stop(self):
self._timer.stop()
self.finished.emit()
def process(self):
print('processing (thread: %r)' % QThread.currentThread())
QThread.sleep(3)
if __name__ == "__main__":
app = QCoreApplication.instance()
if app is None:
app = QApplication(sys.argv)
thread = QThread()
worker = ModbusComWorker()
worker.moveToThread(thread)
def finish():
print('shutting down...')
thread.quit()
thread.wait()
app.quit()
print('stopped')
worker.finished.connect(finish)
thread.started.connect(worker.start)
thread.start()
timer = QTimer()
timer.setSingleShot(True)
timer.timeout.connect(worker.stop)
timer.start(15000)
print('starting (thread: %r)' % QThread.currentThread())
sys.exit(app.exec_())
Output:
starting (thread: <PyQt5.QtCore.QThread object at 0x7f980d096b98>)
processing (thread: <PyQt5.QtCore.QThread object at 0x7f980d0968a0>)
processing (thread: <PyQt5.QtCore.QThread object at 0x7f980d0968a0>)
processing (thread: <PyQt5.QtCore.QThread object at 0x7f980d0968a0>)
processing (thread: <PyQt5.QtCore.QThread object at 0x7f980d0968a0>)
processing (thread: <PyQt5.QtCore.QThread object at 0x7f980d0968a0>)
shutting down...
stopped

PyQt – Signal for object created in different thread doesn't work

I’m implementing multithreading in PyQt5 and Python 3.5 by running a Worker inside a QThread. In the following sample thread2_worker (runs inside a secondary thread and) creates thread3_worker, connects thread3_worker.finished signal to thread3_worker_finished() and runs it.
When thread3_worker is done it emits finished from within its thread but the connection doesn’t work. My guess is that it has to do with thread3_worker being created or connected not in the main thread but I'd welcome any clarification.
import time
from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot, QCoreApplication
class Worker(QObject):
# Generic worker.
finished = pyqtSignal()
def __init__(self, func):
super().__init__()
self.func = func
def run(self):
self.func()
self.finished.emit()
def thread_factory(func):
# Creates a Worker, a QThread and moves the Worker inside the QThread.
worker = Worker(func)
thread = QThread()
worker.moveToThread(thread)
thread.started.connect(worker.run)
# Provision graceful termination.
worker.finished.connect(thread.quit)
worker.finished.connect(worker.deleteLater)
thread.finished.connect(thread.deleteLater)
return worker, thread
def wait():
print("thread3:\t{}".format(QThread.currentThread()))
time.sleep(3)
# finished signal of thread3_worker is never received.
#pyqtSlot()
def thread3_worker_finished():
QCoreApplication.exit()
def create_thread3():
print("thread2:\t{}".format(QThread.currentThread()))
global thread3_worker, thread3
# thread3_worker runs inside thread3, and all it does is call wait().
thread3_worker, thread3 = thread_factory(wait)
thread3_worker.finished.connect(thread3_worker_finished) # FIXME Doesn't work.
thread3.start()
app = QCoreApplication([])
print("Main thread:\t{}".format(QThread.currentThread()))
thread3_worker, thread3 = None, None
# thread2_worker runs inside thread2, and creates and runs thread3_worker.
thread2_worker, thread2 = thread_factory(create_thread3)
thread2.start()
app.exec_()
Cross-thread signals require an event-loop. Your thread_factory function connects the finished signal of the worker to the quit slot of its thread. The quit slot asks the thread to exit its event-loop.
So after thread3 starts, worker2 finishes and thread2 quits. Then when the finished signal of worker3 is emitted, there is no longer an event-loop running that can process it. If you comment out the line worker.finished.connect(thread.quit), your example should work.

PyQt QThread: Destroyed while thread is still running

Despite saving a reference to the QThread as self.lightsThread, stopping the QObject self.lightsWorker then starting self.lightsThread again caused the error
QThread: Destroyed while thread is still running
After stopping self.lightsWorker, must the QThread self.lightsThread be stopped too? If not, what seems to be the problem?
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import time
class Screen(QMainWindow):
def __init__(self):
super(Screen, self).__init__()
self.initUI()
def initUI(self):
self.lightsBtn = QPushButton('Turn On')
self.lightsBtn.setCheckable(True)
self.lightsBtn.setStyleSheet("QPushButton:checked {color: white; background-color: green;}")
self.lightsBtn.clicked.connect(self.lightsBtnHandler)
self.setCentralWidget(self.lightsBtn)
def lightsBtnHandler(self):
if self.lightsBtn.isChecked():
self.startLightsThread()
else:
self.stopLightsThread()
def startLightsThread(self):
print 'start lightsThread'
self.lightsThread = QThread()
self.lightsWorker = LightsWorker()
self.lightsWorker.moveToThread(self.lightsThread)
self.lightsThread.started.connect(self.lightsWorker.work)
self.lightsThread.start()
def stopLightsThread(self):
print 'stop lightsThread'
self.lightsWorker.stop()
class LightsWorker(QObject):
signalFinished = pyqtSignal()
def __init__(self):
QObject.__init__(self)
self._mutex = QMutex()
self._running = True
#pyqtSlot()
def work(self):
while self._running:
print 'working'
time.sleep(1)
self.signalFinished.emit()
#pyqtSlot()
def stop(self):
print 'Stopping'
self._mutex.lock()
self._running = False
self._mutex.unlock()
app = QApplication(sys.argv)
window = Screen()
window.show()
sys.exit(app.exec_())
following the answer https://stackoverflow.com/a/32138213/7742341 after stopping lightWorker you should to quit from the thread and wait untill it is stopped
def stopLightsThread(self):
print('stop lightsThread')
self.lightsWorker.stop()
self.lightsThread.quit()
self.lightsThread.wait()
I had to face the same issue in C++, but the problem is the same.
The problem is that your QThread instance is deleted while the associated thread is still running. This can be really dangerous because the thread code execution is interrupted whitout any guarantee that the thread is ready to be deleted.
For example :
a thread control the execution and life time of an object (a worker)
a ressource is released in this object destructor (explicity or implicilty like when using QObject parent/child system)
since the thread execution is interrupted, the object will not be deleted
It lead to a memory leak and a ressource leak.
In your code, the worker is stopped, but not the working thread. I'm not python expert, but it also seems that your worker object is stopped but not deleted.
To properly stop your worker and thread, you should :
send a message to your worker to tell it to "stop working"
ask your thread to quit : it will post an "exit" message to the thread that will be processed after the worker execution
wait for your thread to stop
The last step is optionnal : if the thread and worker does not share ressources whith other object, you probably don't need to wait them to finish, just forget about them.
The only execption is that all your thread should be properly stopped before exiting the application : you should wait for all current running threads to stop before application exit.
For simple tasks, you should also consider using QtConcurrent framework.

How to avoid the warning “QApplication was not created in main() thread” without having access to the main thread?

I wrote code in PyQt4 that scrapes a website and its inner frames.
import sys, signal
from PyQt4 import QtGui, QtCore, QtWebKit
class Sp():
def save(self, ok, frame=None):
if frame is None:
print ('main-frame')
frame = self.webView.page().mainFrame()
else:
print('child-frame')
print('URL: %s' % frame.baseUrl().toString())
print('METADATA: %s' % frame.metaData())
print('TAG: %s' % frame.documentElement().tagName())
print('HTML: ' + frame.toHtml())
print()
def handleFrameCreated(self, frame):
frame.loadFinished.connect(lambda: self.save(True, frame=frame))
def main(self):
self.webView = QtWebKit.QWebView()
self.webView.page().frameCreated.connect(self.handleFrameCreated)
self.webView.page().mainFrame().loadFinished.connect(self.save)
self.webView.load(QtCore.QUrl("http://www.w3schools.com/tags/tryit.asp?filename=tryhtml_iframe_scrolling"))
signal.signal(signal.SIGINT, signal.SIG_DFL)
print('Press Crtl+C to quit\n')
app = QtGui.QApplication(sys.argv)
s = Sp()
s.main()
sys.exit(app.exec_())
This code depends on creating an instance of QApplication and exiting it accordingly.
The problem with this is that QApplication must be created and exited in the main thread.
I don't have access to the main thread in the project that i'm developing.
Is it possible to avoid the error “QApplication was not created in main() thread” in some way?
Maybe by rewriting the code for it to work without QApplication or somehow make QApplication work without the main thread?
Edit: I can edit the main thread if it doesn't intervene with its flow of the execution of its code, for example app = QtGui.QApplication([]) wouldn't stop the flow but a function that hangs until some code in another thread would finish would be considered inapplicable.

PyQt thread communication help? QThread and QObject

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.

Categories

Resources