According to the developers of Qt it is not recommended to derive directly of QThread
and overwrite their methods exactly as you are doing. Although this practice is quite widespread, there are reasons to avoid it as much as possible. There are many alternatives but, perhaps the most direct, is to create a subclass of QObject
that will act as a worker and use the moveToThread
method.
Remember that you can and should use signals to communicate threads and that you should never directly modify a widget from a thread . The graphical interface should only be drawn and modified from the main thread. If a component of the interface needs to be updated from a secondary thread, signals or any other method that is thread-safe (such as queues) will be used.
On the other hand, remember that QThread
does not run on an independent thread itself, only its run
method does it . QThread
is just a container, you have to be careful with this, any blocking method not directly called by run
will block the interface.
An example of a complete application using Qthread
without using a subclass of it:
import sys
import time
from PyQt5.QtWidgets import QApplication, QPushButton, QMainWindow, QLabel
from PyQt5.QtCore import QObject, QThread, QMutex, pyqtSignal, QRect
class Worker(QObject):
candado = QMutex()
resultado = pyqtSignal(str)
iniciado = pyqtSignal()
terminado = pyqtSignal(bool)
def __init__(self):
super(Worker, self).__init__()
self._trabajando = False
self._abortar = False
def iniciar(self):
self.candado.lock()
self._trabajando = True
self._abortar = False
self.candado.unlock()
self.iniciado.emit()
def abortar(self):
self.candado.lock()
if self._trabajando:
self._abortar = True
self.candado.unlock()
def procesar(self):
for n in range(60):
self.candado.lock()
abortar = self._abortar
self.candado.unlock()
self.resultado.emit('Contador desde hilo: {}'.format(n) )
time.sleep(0.5)
if abortar:
self.resultado.emit('Trabajo en hilo abortado')
break
else:
self.resultado.emit('Trabajo completado')
self.candado.lock()
self._trabajando = False
self.candado.unlock()
self.terminado.emit(True)
class App(QMainWindow):
def __init__(self):
super().__init__()
self.title = 'Ejemplo QThread'
self.left = 100
self.top = 100
self.width = 400
self.height = 300
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
boton_iniciar = QPushButton('Iniciar', self)
boton_iniciar.move(100,120)
boton_iniciar.clicked.connect(self.iniciar_hilo)
boton_detener = QPushButton('Detener', self)
boton_detener.move(210,120)
boton_detener.clicked.connect(self.detener_hilo)
self.etiqueta = QLabel(self)
self.etiqueta.setGeometry(QRect(140, 180, 400, 50))
self.etiqueta.setText('-------------------------')
self.hilo = QThread()
self.worker = Worker()
self.worker.moveToThread(self.hilo)
self.worker.resultado.connect(self.actualizar_etiqueta)
self.worker.iniciado.connect(self.hilo.start)
self.hilo.started.connect(self.worker.procesar)
self.worker.terminado.connect(self.hilo.quit)
def iniciar_hilo(self):
self.worker.iniciar()
def detener_hilo(self):
self.worker.abortar()
def actualizar_etiqueta(self, datos):
self.etiqueta.setText(datos)
if __name__ == '__main__':
app = QApplication(sys.argv)
ventana = App()
ventana.show()
sys.exit(app.exec_())
The application shows an interface with two buttons and a label. One button allows you to throw a thread, the other stops it. The label shows the information processed in the thread.
The process that runs on the thread is just a simple example, a counter from 0 to 59 with half a second of waiting between each number.
The process can be stopped at any time. Qt5 (pyqt5) is used but the idea is valid for Qt4 (pyqt4), you just have to change the import in principle:
import sys
import time
from PyQt4.QtGui import QApplication, QPushButton, QMainWindow, QLabel
from PyQt4.QtCore import QObject, QThread, QMutex, pyqtSignal, QRect
A padlock is used to modify and access the control variables in the thread. Possibly it is not necessary in this case in which, if I am not mistaken, they are atomic operations. However, it is not bad practice to protect variables, memory, files, etc. shared between threads.
Capture of the app worked: