I am using PySide to make a GUI application. I have 3 classes: A QWidget class, a QThread Worker class and a controller for the Worker class. The GUI class creates a controller object that spawns a given number of worker threads, in that hierarchy:
class Worker(QtCore.QThread):
# Signal that tells the controller the current progress of the
# thread.
sig_worker_update_progress = QtCore.Signal(int, int)
def __init__(self, thread_id, *args, **kwargs):
super(Worker, self).__init__(*args, **kwargs)
self.thread_id = thread_id
def run(self):
# Enter
# Loop
self.sig_worker_update_progress.emit(self.thread_id, progress)
# Continue
# Loop
class Controller(QtCore.QObject):
sig_controller_update_progress = QtCore.Signal(int, int)
def __init__(self, num_workers, *args, **kwargs):
super(Controller, self).__init__(*args, **kwargs)
self.workers = []
for i in range(num_workers):
self.workers.append(Worker(i))
self.workers[i].sig_worker_update_progress.connect(
self.slot_worker_update_progress)
for worker in self.workers:
worker.start()
def slot_worker_update_progress(self, thread_id, progress):
# Do
# Stuff
self.sig_controller_update_progress.emit(thread_id, progress)
class Monitor(QtGui.QWidget):
def __init__(self, num_workers, *args, **kwargs):
super(Monitor, self).__init__(*args, **kwargs)
main_layout = QtGui.QVBoxLayout()
self.setLayout(main_layout)
self.progress_bars = []
for _ in range(num_workers):
progress_bar = QtGui.QProgressBar()
main_layout.addWidget(progress_bar)
self.progress_bars.append(progress_bar)
self.controller = Controller(num_workers)
self.controller.sig_controller_update_progress.connect(
self.slot_controller_update_progress)
def slot_controller_update_progress(self, thread_id, progress):
self.progress_bars[thread_id].setValue(progress)
Note that the connect calls appearing above use QtCore.Qt.AutoConnection
as the type
of connection (the default, since the keyword argument is not specified). Absent from the code above is functionality that causes the threads to exit when the user clicks a button on the widget, and other functionality that creates new threads when another button is clicked.
When the interrupt button is clicked, I noticed that some items remain in the emit queue of both Signal
s. This was unexpected, but I found out that this is standard behavior for Qt, since the connections are queued by default. I did not want anything to remain in the queue, so I decided to used a DirectConnection
for both connections. When I did this, my application would run for a few seconds, then crash with the following output:
QPixmap: It is not safe to use pixmaps outside the GUI thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
QPainter::begin: Paint device returned engine == 0, type: 2
QPainter::end: Painter not active, aborted
Segmentation fault (core dumped)
I researched the error. Apparently, threads are not allowed to handle GUI objects directly. The recommended approach is exactly what I have done: to use signals and slots.
Even more curious is the fact that if I use an AutoConnection
for either one of the connections and a DirectConnection
for the other, the application runs fine (save for the residual emit items that confuse my final output).
I need enlightenment from someone more experienced in Qt about what could be happening here.