[edit] 这不是PySide emit signal causes python to crash问题的纯副本。这个问题具体涉及(现在)known bug in PySide preventing None from being passed across threads。另一个问题涉及将信号连接到微调框。我更新了这个问题的标题,以更好地反映我面临的问题。的 [/编辑]
我对PySide与PyQt略有不同的情况感到震惊。好吧,我巧妙地说,但实际上PySide 崩溃了 Python,而PyQt就像我期望的那样工作。
我对PySide完全不熟悉,但对PyQt来说还是比较新的,所以也许我犯了一些基本的错误,但是如果我能搞清楚的话该死的...真的希望你们中的一个好人能给出一些指示! / p>
完整的应用程序是一个批处理工具,在这里描述的过于繁琐,但我已经将问题解决了以下代码示例中的基本要素:
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 *是否正确运行虚函数。或者取消注释第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'
答案 0 :(得分:2)
所以,如果论证是PySide被忽略了,这确实是一个错误,我们不妨提出一个解决方法,对吗?
通过引入一个替换None的sentinel,并且发出 it 可以避免问题,然后在回调中只需要将sentinel交换回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
修改我的原始代码,我们得出了这个解决方案:
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_())
我怀疑这会赢得任何奖项,但似乎确实解决了这个问题。