0

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 Signals. 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.

Community
  • 1
  • 1
nullstellensatz
  • 756
  • 8
  • 18
  • The recommended approach is to use signals and slots *with queued connections*, since that will invoke the slot in the receiver's thread. You say that you do not want anything to remain in the event queue - but are you actually experiencing a specific problem because of this, or is it just a case of premature optimisation? – ekhumoro Dec 29 '14 at 01:38
  • No I am not trying to optimize anything by using a DirectConnection (I understand that it comes with a performance cost since threads have to wait for each other). I need the GUI to show the current progress of all threads when the interrupt button is pressed. This is only possible if every emit item has been delivered. I am sure there are hacky ways of doing this, but I wanted to try DirectConnections first. – nullstellensatz Dec 29 '14 at 02:50
  • Are you seeing a significant lag between the interrupt and the reported progress, then? And exactly how is the interrupt mechanism implemented? This seems to be what your question is really about, but you have omitted most of those details. – ekhumoro Dec 29 '14 at 03:19
  • 1
    @ekhumoro: I would personally say that the recommended appoach is auto, not queued. It will use queued automatically for threads, sure. – László Papp Dec 29 '14 at 05:03
  • The interrupt is not the issue here. My application crashes before the user even has a chance to interact with the GUI (before the interrupt). My question is why a crash happens when I use two DirectConnections, but runs fine when I use one or none. The interrupt process is irrelevant here, but it works as you would expect it to: (1) A signal is sent to the threads (2) The slot for the signal causes a boolean to flip (2) The threads check for the boolean value and exit the loop. – nullstellensatz Dec 29 '14 at 09:49
  • Another question is why I am getting an error that seems unrelated to what I am doing. I am clearly not trying to update the GUI from the threads. And the pixmap error goes away when I change the type of the connection. I understand that AutoConnections are recommended, but that is besides the issue. – nullstellensatz Dec 29 '14 at 09:54
  • You can find an explanation [on a follow-up question][1] [1]: http://stackoverflow.com/questions/27690865/nested-directconnection-signals-are-causing-a-segfault/27697502#27697502 – nullstellensatz Dec 29 '14 at 23:31

0 Answers0