2014-05-19 4 views
6

[Bearbeiten] Dies ist kein reines Duplikat der PySide emit signal causes python to crash Frage. Diese Frage bezieht sich speziell auf ein (jetzt) ​​known bug in PySide preventing None from being passed across threads. Die andere Frage betrifft das Anschließen von Signalen an eine Drehfeldbox. Ich habe den Titel dieser Frage aktualisiert, um das Problem, dem ich gegenüberstand, besser widerzuspiegeln. PySide Absturz Python bei der Ausgabe keine zwischen Threads

Ich habe meinen Kopf gegen eine Situation geknallt, in der sich PySide subtil von PyQt unterscheidet. Nun, ich sage subtil, aber tatsächlich PySide stürzt Python, während PyQt funktioniert, wie ich es erwarte.

Under PySide my app crashes

Ich bin völlig neu zu pyside und noch ziemlich neu in PyQt so vielleicht einige grundlegenden Fehler, den ich mache, aber verdammt, wenn ich es herausfinden kann ... wirklich von euch Hoffnung, fein Leute kann ein paar Hinweise geben!

Die vollständige App ist ein Batch-Verarbeitung Werkzeug und viel zu umständlich hier zu beschreiben, aber ich habe das Problem auf das Wesentliche im Code-Beispiel unten abgestreift:

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

Ausführen dieses eine Anzeige Fenster mit einem "do it" -Knopf. Wenn Sie darauf klicken, stürzt Python ab, wenn Sie unter PySide laufen. Kommentieren Sie die Zeile ImportError in Zeile 4, damit PyQt * die Dummy-Funktion korrekt ausführt. Oder entmarkieren Sie die return Anweisung in Zeile 20, um PySide korrekt zu sehen.

Ich verstehe nicht, warum emittiert None Python/PySide so schlecht ausfallen?

Das Ziel ist es, die Verarbeitung (was auch immer Dummy tut) auf einen anderen Thread zu verschieben, wobei der Haupt-GUI-Thread reaktionsbereit bleibt. Auch das hat mit PyQt gut funktioniert, aber eindeutig nicht mit PySide.

Alle und alle Ratschläge werden sehr geschätzt.

Dies ist unter:

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

Ich bin mir nicht sicher, dass es ein Duplikat dieser Frage ist. Zum Beispiel verursacht das Ausgeben eines Tupels, das '(None,)' enthält, keinen Absturz, aber das direkte Senden von 'None' stürzt ab.Es scheint ein Problem zu sein, bei dem 'None' nicht vom Typ' Objekt' ist, vielleicht? Ich hätte erwartet, dass, um eine Ausnahme zu erheben, nicht hart Python abstürzen. –

+0

Mein Kollege hat diesen pyside Fehler, der das Problem scheint genau abdecken: https://bugreports.qt-project.org/browse/PYSIDE-17 > Segfault, wenn ein Signal mit einem Keinem Parameter von einer anderen emittierenden mich irgendwie Ich habe das in meinem Googlin nicht gefunden. Erstellt im Jahr 2012, also .. hm. –

+1

Gut finden. Ich bezweifle, dass es in absehbarer Zeit behoben wird (PySide-Entwicklung scheint ziemlich stagnierend). Mein Vorschlag wäre, alles aufzuwickeln. Ich bin ein Tupel, gebe das Signal aus und entpacke es auf der anderen Seite. Oder bleib bei der Verwendung von PyQt4: p –

Antwort

2

Also, wenn das Argument, dass pyside vernachlässigt wird und das ist wirklich ein Problem, können wir auch mit einer Vermeidung des Problems kommen, nicht wahr?

Durch die Einführung einer Sentinel zu ersetzen, keine, und emittieren es das Problem kann umgangen werden, dann die Sentinel muss nur zurück zu keiner in den Rückrufen und das Problem wird umgangen.

Gute Trauer obwohl. Ich poste den Code, mit dem ich geendet habe, um weitere Kommentare einzuladen, aber wenn Sie bessere Alternativen oder tatsächliche Lösungen erhalten, dann geben Sie einen Schrei. In der Zwischenzeit werde ich denke, dies zu tun:

_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 

Ändern meiner ursprünglichen Code kommen wir zu dieser Lösung:

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

Ich bezweifle, dass alle Preise gewinnen werde, aber es scheint das zu beheben Problem.

+0

Hätte ich diesen Beitrag erst vor 3 Tagen gelesen. Die Firma, für die ich arbeite, und ich, ich danke Ihnen sehr herzlich. Dies funktioniert wie erwartet. Vielleicht ein bisschen hacky, aber ohne dass PySide in letzter Zeit irgendwelche Aufmerksamkeit auf sich zieht, wird dieser Bug wahrscheinlich bestehen bleiben. – Halsafar