【问题标题】:PySide crashing Python when emitting None between threadsPySide 在线程之间发出 None 时崩溃 Python
【发布时间】:2014-07-06 20:40:53
【问题描述】:

[编辑] 这不是PySide emit signal causes python to crash 问题的纯粹重复。这个问题特别涉及(现在)known bug in PySide preventing None from being passed across threads。另一个问题与将信号连接到微调框有关。我已经更新了这个问题的标题,以更好地反映我所面临的问题。 [/edit]

我遇到了 PySide 的行为与 PyQt 略有不同的情况。好吧,我巧妙地说,但实际上 PySide 崩溃 Python 而 PyQt 可以正常工作。

我对 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_()

运行它会显示一个带有“执行”按钮的窗口。如果在 PySide 下运行,单击它会使 Python 崩溃。取消注释第 4 行上的 ImportError 以查看 PyQt* 正确运行 Dummy 函数。或者取消注释第 20 行的 return 语句以查看 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'

【问题讨论】:

  • 我不确定它是否与该问题重复。例如,发出包含(None,) 的元组不会导致崩溃,但直接发出None 会导致崩溃。这似乎是一个问题,None 不被认为是object 类型,也许吧?我本来希望这会引发异常,而不是 Python 硬崩溃。
  • 我的同事发现了这个似乎完全涵盖了问题的 PySide 错误:bugreports.qt-project.org/browse/PYSIDE-17 > 当从另一个发出带有 None 参数的信号时出现 Segfault 我不知何故在我的谷歌搜索中没有找到它。创建于 2012 年,所以.. 嗯。
  • 很好的发现。我怀疑它会很快得到修复(PySide 的开发似乎相当停滞)。我的建议是将所有内容都包装起来,我是一个元组,发出信号,然后在另一边解包。或者坚持使用 PyQt4:p
  • 是的,一个有效的建议。我在下面添加了一个类似的解决方法,不知道如果回答自己的问题是不好的形式,但除非弹出其他问题,否则我会勾选这个。
  • 鼓励回答您自己的问题 :) 我也曾尝试要求重新提出这个问题。它真的不是重复的......

标签: python crash pyqt pyside


【解决方案1】:

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

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

虽然很遗憾。我将发布我最终得到的代码以邀请更多的 cmets,但如果您有更好的替代方案或实际解决方案,请大声疾呼。与此同时,我想这会做:

_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

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

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_())

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

【讨论】:

  • 如果我在 3 天前读过这篇文章就好了。我工作的公司和我自己非常感谢你们。这按预期工作。也许有点 hacky,但最近 PySide 没有引起任何关注,这个 bug 很可能会一直存在。
猜你喜欢
  • 2013-08-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-12
  • 2011-02-18
  • 2016-10-26
  • 1970-01-01
相关资源
最近更新 更多