PySide在线程之间发出None时崩溃Python

Jon*_*sen 6 python crash pyqt pyside

[编辑]这不是PySide发出信号的纯副本导致python崩溃的问题.这个问题特别涉及PySide中的一个(现在)已知错误,它阻止了无法跨线程传递.另一个问题涉及将信号连接到微调框.我更新了这个问题的标题,以更好地反映我面临的问题.[/编辑]

我对PySide与PyQt略有不同的情况感到震惊.好吧,我巧妙地说,但实际上PySide 崩溃了 Python,而PyQt正如我所期望的那样工作.

在PySide下我的应用程序崩溃了

我是PySide的新手,对PyQt来说还是比较新的,所以也许我犯了一些基本的错误,但是如果我能搞清楚的话该死的...真的希望你们中的一个好人能给出一些指示!

完整的应用程序是一个批处理工具,在这里描述太麻烦了,但我已经将问题解决了以下代码示例中的基本要点:

import threading

try:
    # raise ImportError()  # Uncomment this line to show PyQt works correctly
    from PySide import QtCore, QtGui
except ImportError:
    from PyQt4 import QtCore, QtGui
    QtCore.Signal = QtCore.pyqtSignal
    QtCore.Slot = QtCore.pyqtSlot


class _ThreadsafeCallbackHelper(QtCore.QObject):
    finished = QtCore.Signal(object)


def Dummy():
    print "Ran Dummy"
    # return ''  # Uncomment this to show PySide *not* crashing
    return None


class BatchProcessingWindow(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self, None)

        btn = QtGui.QPushButton('do it', self)
        btn.clicked.connect(lambda: self._BatchProcess())

    def _BatchProcess(self):
        def postbatch():
            pass
        helper = _ThreadsafeCallbackHelper()
        helper.finished.connect(postbatch)

        def cb():
            res = Dummy()
            helper.finished.emit(res)  # `None` crashes Python under PySide??!
        t = threading.Thread(target=cb)
        t.start()


if __name__ == '__main__':  # pragma: no cover
    app = QtGui.QApplication([])
    BatchProcessingWindow().show()
    app.exec_()
Run Code Online (Sandbox Code Playgroud)

运行它会显示一个带有"执行"按钮的窗口.如果在PySide下运行,单击它会导致Python崩溃.取消注释第ImportError4行,看看PyQt*是否正确运行Dummy函数.或者取消注释第return20行的语句,看看PySide是否正确运行.

我不明白为什么发射None使Python/PySide失败如此严重?

目标是将处理(无论Dummy做什么)卸载到另一个线程,保持主GUI线程响应.再次,这与PyQt一起运行良好,但与PySide显然没有那么多.

任何和所有的建议将非常感激.

这是在:

    Python 2.7 (r27:82525, Jul  4 2010, 09:01:59) [MSC v.1500 32 bit (Intel)] on win32

    >>> import PySide
    >>> PySide.__version_info__
    (1, 1, 0, 'final', 1)

    >>> from PyQt4 import Qt
    >>> Qt.qVersion()
    '4.8.2'
Run Code Online (Sandbox Code Playgroud)

Jon*_*sen 2

因此,如果争论是 PySide 被忽略并且这确实是一个错误,我们不妨想出一个解决方法,对吧?

通过引入哨兵来替换 None,并发出它,可以规避问题,然后只需在回调中将哨兵交换回 None 即可绕过问题。

不过好悲伤。我将发布我最终得到的代码以邀请进一步的评论,但如果您有更好的替代方案或实际的解决方案,请大声喊叫。与此同时,我想这会做:

_PYSIDE_NONE_SENTINEL = object()


def pyside_none_wrap(var):
    """None -> sentinel. Wrap this around out-of-thread emitting."""
    if var is None:
        return _PYSIDE_NONE_SENTINEL
    return var


def pyside_none_deco(func):
    """sentinel -> None. Decorate callbacks that react to out-of-thread
    signal emitting.

    Modifies the function such that any sentinels passed in
    are transformed into None.
    """

    def sentinel_guard(arg):
        if arg is _PYSIDE_NONE_SENTINEL:
            return None
        return arg

    def inner(*args, **kwargs):
        newargs = map(sentinel_guard, args)
        newkwargs = {k: sentinel_guard(v) for k, v in kwargs.iteritems()}
        return func(*newargs, **newkwargs)

    return inner
Run Code Online (Sandbox Code Playgroud)

修改我的原始代码,我们得到这个解决方案:

class _ThreadsafeCallbackHelper(QtCore.QObject):
    finished = QtCore.Signal(object)


def Dummy():
    print "Ran Dummy"
    return None


def _BatchProcess():
    @pyside_none_deco
    def postbatch(result):
        print "Post batch result: %s" % result

    helper = _ThreadsafeCallbackHelper()
    helper.finished.connect(postbatch)

    def cb():
        res = Dummy()
        helper.finished.emit(pyside_none_wrap(res))

    t = threading.Thread(target=cb)
    t.start()


class BatchProcessingWindow(QtGui.QDialog):
    def __init__(self):
        super(BatchProcessingWindow, self).__init__(None)

        btn = QtGui.QPushButton('do it', self)
        btn.clicked.connect(_BatchProcess)


if __name__ == '__main__':  # pragma: no cover
    app = QtGui.QApplication([])
    window = BatchProcessingWindow()
    window.show()
    sys.exit(app.exec_())
Run Code Online (Sandbox Code Playgroud)

我怀疑这会赢得任何奖项,但它似乎确实解决了这个问题。