How to call widget slots from QThread worker?
I know I could create a signal for each widget's slot like this:
class App(QtWidgets.QMainWindow):
signal_line_edit_1_setText = pyqtSignal(str)
signal_line_edit_2_setText = pyqtSignal(str)
...
def __init__(self):
...
self.signal_line_edit_1_setText.connect(self.line_edit_1.setText)
self.signal_line_edit_2_setText.connect(self.line_edit_2.setText)
self.worker = Worker(self)
class Worker(QThread):
# Maybe I have to create signals for Worker class and then connect them to app's signals,
# but that would be even more complicated
def __init__(self, app):
self.app = app
...
def run(self):
self.app.signal_line_edit_1_setText.emit('Worker Running')
...
Isn't there a more simple way to thread-safely interact with widgets?
QTimer works without signal wrapping, but it makes UI just a little laggy.
I know about QThreadPool, but don't really understand it.
There is a way to do exactly what you want, but it's much less commonly used than custom signals (in Python anyway - I'm not so sure about C++).
The relevant API is QMetaObject.invokeMethod. This permits thread-safe calling of any method of a QObject subclass, so long as the method is accessible via the Qt Meta Object System. In practise, this will usually limit it to pre-existing Qt methods, plus user-defined methods wrapped with the pyqtSlot decorator. Here is what a typical usage looks like:
QtCore.QMetaObject.invokeMethod(widget, 'mySlot', QtCore.Q_ARG(int, number))
As you can see, it looks quite clunky in comparison with PyQt's new-style signal and slot syntax, and in fact its disadvantages are similar to those of the old-style signal and slot syntax: namely, that it's somewhat error-prone, verbose and not very pythonic. Its only significant advantage (relative to the current use-case) is that it avoids having to pre-define a signal. (See below for a demo script that uses both approaches to ensure GUI updates are carried out in the main thread).
I suppose it would be possible to create a custom class that automagically invoked methods across threads using this approach (perhaps via __getattr__). But why bother going to all the trouble of developing and maintaining such a class when there's already a built-in mechanism that achieves much the same thing? Defining a custom signal, connecting it to a callable, and emitting it isn't at all complicated:
class Worker(QThread):
customSignal = pyqtSignal(int)
def run(self):
self.customSignal.emit(42)
worker = Worker()
worker.customSignal.connect(lambda x: print(x))
worker.start()
and the resulting code is very readable, flexible and easy to maintain.
Demo Script:
from PyQt5 import QtCore, QtWidgets
def thread_id():
return int(QtCore.QThread.currentThreadId())
class Worker(QtCore.QThread):
progressChanged = QtCore.pyqtSignal(int)
def setMethod(self, invoke=False):
self._invoke = invoke
def run(self):
print()
print(f'Thread: {MAIN_THREAD} [Main]')
print(f'Thread: {thread_id()} [Worker.run]')
invoke = getattr(self, '_invoke', False)
print('Using Method:', 'invoke' if invoke else 'signal')
for count in range(1, 6):
self.msleep(500)
if invoke:
QtCore.QMetaObject.invokeMethod(
self.parent(), 'updateProgress', QtCore.Q_ARG(int, count))
else:
self.progressChanged.emit(count)
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.button = QtWidgets.QPushButton('Test')
self.button.clicked.connect(self.handleButton)
self.check = QtWidgets.QCheckBox('Use inkoke')
self.label = QtWidgets.QLabel()
self.label.setAlignment(QtCore.Qt.AlignCenter)
layout = QtWidgets.QGridLayout(self)
layout.addWidget(self.label, 0, 0, 1, 2)
layout.addWidget(self.button, 1, 0)
layout.addWidget(self.check, 1, 1)
self.worker = Worker(self)
self.worker.progressChanged.connect(self.updateProgress)
self.updateProgress()
def handleButton(self):
if not self.worker.isRunning():
self.updateProgress()
self.worker.setMethod(invoke=self.check.isChecked())
self.worker.start()
#QtCore.pyqtSlot(int)
def updateProgress(self, count=0):
if count:
print(f'Thread: {MAIN_THREAD} [Main]')
print(f'Thread: {thread_id()} [Window.updateProgress]')
self.label.setText(f'Count: {count}')
def closeEvent(self, event):
self.worker.quit()
self.worker.wait()
app = QtWidgets.QApplication(['Test'])
MAIN_THREAD = thread_id()
print(f'Thread: {MAIN_THREAD} [Main]')
window = Window()
window.setGeometry(600, 100, 300, 200)
window.show()
app.exec_()
Related
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 want to be able to instantiate a number of QWidget() type classes, each in a separate process. Like Google Chrome does when you open a new tab.
Is this possible in python ?
GUI elements (including QWidget) can only be created in the main thread.
However, you could put the model and business logic for each tab in a separate thread, and have each communicate with the main thread using Signals and Slots. The Qt documentation for QThreads provides an example of the Worker Pattern for doing this.
This way, if any of the worker processes become hung, it won't effect the responsiveness of your main GUI thread.
class MyTab(QtGui.QWidget):
def __init__(self, parent):
...
self.worker = Worker()
self.thread = QtCore.QThread(self)
self.worker.moveToThread(self.thread)
self.worker.resultReady.connect(self.handleResult)
self.thread.start()
def callSomeFunction(self):
QtCore.QMetaObject.invokeMethod(self.worker, 'someFunction', QtCore.Qt.QueuedConnection, QtCore.Q_ARG(str, 'arg1'))
#QtCore.pyqtSlot(object)
def handleResult(self, result):
... # do stuff with result
class Worker(QtCore.QObject):
resultReady = QtCore.pyqtSignal(object)
#QtCore.pyqtSlot(str)
def someFunction(self, arg):
...
self.resultReady.emit({'func': 'someFunction', 'result': True})
I would like to know what are the consequences of emitting a signal from a regular python thread within a QObject, compared with a QThread.
See the following class:
class MyObject(QtCore.QObject):
def __init__(self):
super().__init__()
sig = pyqtSignal()
def start(self):
self._thread = Thread(target=self.run)
self._thread.start()
def run(self):
self.sig.emit()
# Do something
Now, assuming that in the GUI thread, I have:
def __init__(self):
self.obj = MyObject()
self.obj.sig.connect(self.slot)
self.obj.start()
def slot(self):
# Do something
the slot is indeed executed when the signal is emitted. However, I would like to know which thread will the slot method be executed in? Would it be any different if I used a QThread instead of a python thread in MyObject?
I am using PyQt5 and Python 3.
By default, Qt automatically queues signals when they are emitted across threads. To do this, it serializes the signal parameters and then posts an event to the event-queue of the receiving thread, where any connected slots will eventually be executed. Signals emitted in this way are therefore guaranteed to be thread-safe.
With regard to external threads, the Qt docs state the following:
Note: Qt's threading classes are implemented with native threading
APIs; e.g., Win32 and pthreads. Therefore, they can be used with
threads of the same native API.
In general, if the docs state that a Qt API is thread-safe, that guarantee applies to all threads that were created using the same native library - not just the ones that were created by Qt itself. This means it is also safe to explicitly post events to other threads using such thread-safe APIs as postEvent() and invoke().
There is therefore no real difference between using threading.Thread and QThread when it comes to emitting cross-thread signals, so long as both Python and Qt use the same underlying native threading library. This suggests that one possible reason to prefer using QThread in a PyQt application is portability, since there will then be no danger of mixing incompatible threading implementations. However, it is highly unlikely that this issue will ever arise in practice, given that both Python and Qt are deliberately designed to be cross-platform.
As to the question of which thread the slot will be executed in - for both Python and Qt, it will be in the main thread. By contrast, the run method will be executed in the worker thread. This is a very important consideration when doing multi-threading in a Qt application, because it is not safe to perform gui operations outside the main thread. Using signals allows you to safely communicate between the worker thread and the gui, because the slot connected to the signal emitted from the worker will be called in the main thread, allowing you to update the gui there if necessary.
Below is a simple script that shows which thread each method is called in:
import sys, time, threading
from PyQt5 import QtCore, QtWidgets
def thread_info(msg):
print(msg, int(QtCore.QThread.currentThreadId()),
threading.current_thread().name)
class PyThreadObject(QtCore.QObject):
sig = QtCore.pyqtSignal()
def start(self):
self._thread = threading.Thread(target=self.run)
self._thread.start()
def run(self):
time.sleep(1)
thread_info('py:run')
self.sig.emit()
class QtThreadObject(QtCore.QThread):
sig = QtCore.pyqtSignal()
def run(self):
time.sleep(1)
thread_info('qt:run')
self.sig.emit()
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.pyobj = PyThreadObject()
self.pyobj.sig.connect(self.pyslot)
self.pyobj.start()
self.qtobj = QtThreadObject()
self.qtobj.sig.connect(self.qtslot)
self.qtobj.start()
def pyslot(self):
thread_info('py:slot')
def qtslot(self):
thread_info('qt:slot')
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(600, 100, 300, 200)
window.show()
thread_info('main')
sys.exit(app.exec_())
Output:
main 140300376593728 MainThread
py:run 140299947104000 Thread-1
py:slot 140300376593728 MainThread
qt:run 140299871450880 Dummy-2
qt:slot 140300376593728 MainThread
I would like to add:
class MyQThread(QThread):
signal = pyqtSignal() # This thread emits this at some point.
class MainThreadObject(QObject):
def __init__(self):
thread = MyQThread()
thread.signal.connect(self.mainThreadSlot)
thread.start()
#pyqtSlot()
def mainThreadSlot(self):
pass
This is perfectly OK, according to all documentation I know of. As is the following:
class MyQObject(QObject):
signal = pyqtSignal()
class MainThreadObject(QObject):
def __init__(self):
self.obj = MyQObject()
self.obj.signal.connect(self.mainThreadSlot)
self.thread = threading.Thread(target=self.callback)
self.thread.start()
def callback(self):
self.obj.signal.emit()
#pyqtSlot()
def mainThreadSlot(self):
pass
From what #ekhumoro is saying, those two are functionally the same thing. Because a QThread is just a QObject who's run() method is the target= of a threading.Thread.
In other words, both the MyQThread's and the MyQObject's signal is memory "owned" by the main thread, but accessed from child threads.
Therefore the following should also be safe:
class MainThreadObject(QObject):
signal = pyqtSignal() # Connect to this signal from QML or Python
def __init__(self):
self.thread = threading.Thread(target=self.callback)
self.thread.start()
def callback(self):
self.signal.emit()
Please correct me if I am wrong. It would be very nice to have official documentation on this behavior from Qt and/or Riverbank.
I'm trying to learn how to use QThreads in a PyQt Gui application. I have stuff that runs for a while, with (usually) points where I could update a Gui, but I would like to split the main work out to its own thread (sometimes stuff gets stuck, and it would be nice to eventually have a cancel/try again button, which obviously doesn't work if the Gui is frozen because the Main Loop is blocked).
I've read https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/. That page says that re-implementing the run method is not the way to do it. The problem I am having is finding a PyQt example that has a main thread doing the Gui and a worker thread that does not do it that way. The blog post is for C++, so while it's examples do help, I'm still a little lost. Can someone please point me to an example of the right way to do it in Python?
Here is a working example of a separate worker thread which can send and receive signals to allow it to communicate with a GUI.
I made two simple buttons, one which starts a long calculation in a separate thread, and one which immediately terminates the calculation and resets the worker thread.
Forcibly terminating a thread as is done here is not generally the best way to do things, but there are situations in which always gracefully exiting is not an option.
from PyQt4 import QtGui, QtCore
import sys
import random
class Example(QtCore.QObject):
signalStatus = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
super(self.__class__, self).__init__(parent)
# Create a gui object.
self.gui = Window()
# Create a new worker thread.
self.createWorkerThread()
# Make any cross object connections.
self._connectSignals()
self.gui.show()
def _connectSignals(self):
self.gui.button_cancel.clicked.connect(self.forceWorkerReset)
self.signalStatus.connect(self.gui.updateStatus)
self.parent().aboutToQuit.connect(self.forceWorkerQuit)
def createWorkerThread(self):
# Setup the worker object and the worker_thread.
self.worker = WorkerObject()
self.worker_thread = QtCore.QThread()
self.worker.moveToThread(self.worker_thread)
self.worker_thread.start()
# Connect any worker signals
self.worker.signalStatus.connect(self.gui.updateStatus)
self.gui.button_start.clicked.connect(self.worker.startWork)
def forceWorkerReset(self):
if self.worker_thread.isRunning():
print('Terminating thread.')
self.worker_thread.terminate()
print('Waiting for thread termination.')
self.worker_thread.wait()
self.signalStatus.emit('Idle.')
print('building new working object.')
self.createWorkerThread()
def forceWorkerQuit(self):
if self.worker_thread.isRunning():
self.worker_thread.terminate()
self.worker_thread.wait()
class WorkerObject(QtCore.QObject):
signalStatus = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
super(self.__class__, self).__init__(parent)
#QtCore.pyqtSlot()
def startWork(self):
for ii in range(7):
number = random.randint(0,5000**ii)
self.signalStatus.emit('Iteration: {}, Factoring: {}'.format(ii, number))
factors = self.primeFactors(number)
print('Number: ', number, 'Factors: ', factors)
self.signalStatus.emit('Idle.')
def primeFactors(self, n):
i = 2
factors = []
while i * i <= n:
if n % i:
i += 1
else:
n //= i
factors.append(i)
if n > 1:
factors.append(n)
return factors
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.button_start = QtGui.QPushButton('Start', self)
self.button_cancel = QtGui.QPushButton('Cancel', self)
self.label_status = QtGui.QLabel('', self)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.button_start)
layout.addWidget(self.button_cancel)
layout.addWidget(self.label_status)
self.setFixedSize(400, 200)
#QtCore.pyqtSlot(str)
def updateStatus(self, status):
self.label_status.setText(status)
if __name__=='__main__':
app = QtGui.QApplication(sys.argv)
example = Example(app)
sys.exit(app.exec_())
You are right that it is a good thing to have a worker thread doing the processing while main thread is doing the GUI. Also, PyQt is providing thread instrumentation with a signal/slot mechanism that is thread safe.
This may sound of interest. In their example, they build a GUI
import sys, time
from PyQt4 import QtCore, QtGui
class MyApp(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setGeometry(300, 300, 280, 600)
self.setWindowTitle('threads')
self.layout = QtGui.QVBoxLayout(self)
self.testButton = QtGui.QPushButton("test")
self.connect(self.testButton, QtCore.SIGNAL("released()"), self.test)
self.listwidget = QtGui.QListWidget(self)
self.layout.addWidget(self.testButton)
self.layout.addWidget(self.listwidget)
def add(self, text):
""" Add item to list widget """
print "Add: " + text
self.listwidget.addItem(text)
self.listwidget.sortItems()
def addBatch(self,text="test",iters=6,delay=0.3):
""" Add several items to list widget """
for i in range(iters):
time.sleep(delay) # artificial time delay
self.add(text+" "+str(i))
def test(self):
self.listwidget.clear()
# adding entries just from main application: locks ui
self.addBatch("_non_thread",iters=6,delay=0.3)
(simple ui containing a list widget which we will add some items to by clicking a button)
You may then create our own thread class, one example is
class WorkThread(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
def __del__(self):
self.wait()
def run(self):
for i in range(6):
time.sleep(0.3) # artificial time delay
self.emit( QtCore.SIGNAL('update(QString)'), "from work thread " + str(i) )
self.terminate()
You do redefine the run() method. You may find an alternative to terminate(), see the tutorial.
In my opinion, by far the best explanation, with example code which is initially unresponsive, and is then improved, is to be found here.
Note that this does indeed use the desired (non-subclassed) QThread and moveToThread approach, which the article claims to be the preferred approach.
The above linked page also provides the PyQt5 equivalent to the C Qt page giving the definitive explanation by Maya Posch from 2011. I think she was probably using Qt4 at the time, but that page is still applicable in Qt5 (hence PyQt5) and well worth studying in depth, including many of the comments (and her replies).
Just in case the first link above one day goes 404 (which would be terrible!), this is the essential Python code which is equivalent to Maya's C code:
self.thread = QtCore.QThread()
# Step 3: Create a worker object
self.worker = Worker()
# Step 4: Move worker to the thread
self.worker.moveToThread(self.thread)
# Step 5: Connect signals and slots
self.thread.started.connect(self.worker.run)
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
self.worker.progress.connect(self.reportProgress)
# Step 6: Start the thread
self.thread.start()
# Final resets
self.longRunningBtn.setEnabled(False)
self.thread.finished.connect(
lambda: self.longRunningBtn.setEnabled(True)
)
self.thread.finished.connect(
lambda: self.stepLabel.setText("Long-Running Step: 0")
)
NB self in the example on that page is the QMainWindow object. I think you may have to be quite careful about what you attach QThread instances to as properties: instances which are destroyed when they go out of scope, but which have a QThread property, or indeed a local QThread instance which goes out of scope, seem to be capable of causing some inexplicable Python crashes, which aren't picked up by sys.excepthook (or the sys.unraisablehook). Caution advised.
... where Worker looks something like this:
class Worker(QtCore.QObject):
finished = QtCore.pyqtSignal()
progress = QtCore.pyqtSignal(int)
def run(self):
"""Long-running task."""
for i in range(5):
sleep(1)
self.progress.emit(i + 1)
self.finished.emit()