Brendan Abel Brendan Abel - 1 month ago 25
Python Question

How to send None with Signals across threads?

I've implemented a version of the worker pattern that is described in the Qt Threading docs.

I'm using

Signals/Slots
to send data between the worker thread and the main thread.

When defining the
Signal
, I've set the argument signature type to
object
since I believe it should allow me to pass any python object through the
Signal
.

result_ready = QtCore.Signal(object)


However, when I try to pass
None
through the
Signal
it crashes python. This only happens when trying to pass the
Signal
across threads. If I comment out the
self.worker.moveToThread(self.thread)
line, it works and
None
is successfully passed through the
Signal
.

Why am I unable to pass
None
in this instance?

I'm using
PySide 1.2.2
and
Qt 4.8.5
.

import sys

from PySide import QtCore, QtGui


class Worker(QtCore.QObject):

result_ready = QtCore.Signal(object)

@QtCore.Slot()
def work(self):
print 'In Worker'

# This works
self.result_ready.emit('Value')

# This causes python to crash
self.result_ready.emit(None)


class Main(QtGui.QWidget):

def __init__(self):
super(Main, self).__init__()
self.ui_lay = QtGui.QVBoxLayout()
self.setLayout(self.ui_lay)
self.ui_btn = QtGui.QPushButton('Test', self)
self.ui_lay.addWidget(self.ui_btn)
self.ui_lay.addStretch()
self.setGeometry(400, 400, 400, 400)
self.worker = Worker()
self.thread = QtCore.QThread(self)
self.worker.moveToThread(self.thread)
self.thread.start()
self.ui_btn.clicked.connect(self.worker.work)
self.worker.result_ready.connect(self.handle_worker_result)

@QtCore.Slot(object)
def handle_worker_result(self, result=None):
print 'Handling output', result

def closeEvent(self, event):
self.thread.quit()
self.thread.wait()
super(Main, self).closeEvent(event)


if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
obj = Main()
obj.show()
app.exec_()

Answer

This looks like a PySide bug. The same example code works exactly as expected with PyQt4.

The issue is with the type of signal connection. For cross-thread signals, this will use a QueuedConnection unless you specify otherwise. If the connection type is changed to DirectConnection in the example code, it will work as expected - but of course it won't be thread-safe anymore.

A QueuedConnection will post an event to the event-queue of the receiving thread. But in order for this to be thread-safe, Qt has to serialize the emitted arguments. However, PySide will obviously need to inject some magic here to deal with python types that Qt doesn't know anything about. If I had to guess, I would bet that PySide is mistakenly converting the python None object to a C++ NULL pointer, which will obviously have nasty consequences later on.

If you want to work around this, I suppose you could emit your own sentinel object as a placeholder for None.

UPDATE:

Found the bug, PYSIDE-17, which was posted in March 2012! Sadly, the suggested patch seems to have never been reviewed.

Comments