2013-12-23 9 views
7

Ich habe den folgenden Code, der eine Hintergrundoperation ausführt (scan_value) während der Aktualisierung eines Fortschrittsbalkens in der ui (progress). scan_value iteriert über einen Wert in obj und gibt jedes Mal, wenn der Wert geändert wird, ein Signal aus (value_changed). Aus Gründen, die hier nicht relevant sind, muss ich diese in ein Objekt (Scanner) in einem anderen Thread einbinden. Der Scanner wird aufgerufen, wenn die a-Taste scanclicked ist. Und hier kommt meine Frage ... der folgende Code funktioniert gut (d. H. Der Fortschrittsbalken wird rechtzeitig aktualisiert).PyQt: Anschließen eines Signals an einen Steckplatz zum Starten einer Hintergrundoperation

# I am copying only the relevant code here. 

def update_progress_bar(new, old): 
    fraction = (new - start)/(stop - start) 
    progress.setValue(fraction * 100) 

obj.value_changed.connect(update_progress_bar) 

class Scanner(QObject): 

    def scan(self): 
     scan_value(start, stop, step) 
     progress.setValue(100) 

thread = QThread() 
scanner = Scanner() 
scanner.moveToThread(thread) 
thread.start() 

scan.clicked.connect(scanner.scan) 

Aber wenn ich den letzten Teil dazu ändern:

thread = QThread() 
scanner = Scanner() 
scan.clicked.connect(scanner.scan) # This was at the end! 
scanner.moveToThread(thread) 
thread.start() 

Der Fortschrittsbalken erst am Ende aktualisiert wird (meine Vermutung ist, dass alles auf dem gleichen Thread ausgeführt wird). Sollte es irrelevant sein, wenn ich das Signal vor oder nach dem Verschieben des Objekts, das das Objekt empfängt, an den Thread anschließe.

+2

Sieht aus wie ekhumoro ist richtig (pyqt/qt scheint den Verbindungstyp nicht automatisch zu erkennen, es sei denn, Sie schmücken Ihre Slots explizit mit @pyqtSlot()). Ich möchte jedoch darauf hinweisen, dass die Zeile 'progress.setValue (100)' Thread ** unsich ist **, weil Sie von einem anderen Thread als dem Hauptthread auf ein Qt GUI-Objekt zugreifen. Der Rest Ihres geposteten Codes ist Thread-sicher in Bezug auf Qt GUI-Operationen –

+1

@ three_pineapples. Es wäre interessant zu wissen, ob es hier einen PyQt-Bug gibt, oder ob es nur eine Besonderheit von PyQt ist, die sich mit Python-Callables verbindet. Ich weiß, dass eine Art Proxy-Objekt erstellt wird, wenn '@ pyqtSlot' nicht verwendet wird, aber genau, welche Konsequenzen dies für Warteschlangenverbindungen hat, weiß ich nicht. – ekhumoro

+1

@ekhumoro Ich denke, es könnte ein PyQt4 Bug sein, oder zumindest ein Mangel, der behoben werden sollte. Es zeigt sicherlich nicht das gleiche Verhalten in PySide (PySide führt immer die "Scan" -Funktion im QThread aus, unabhängig davon, wo das Signal verbunden war oder wie der Slot dekoriert ist). Ich habe ein minimilistic Beispiel hier http://pastebin.com/SqP3WM1z gemacht, das ausdruckt, in welchem ​​Faden Sachen laufen. –

Antwort

10

Es sollte egal sein, ob die Verbindung vor oder nach dem Verschieben des Worker-Objekts auf den anderen Thread hergestellt wird. Ein Zitat aus dem Qt docs:

Qt :: Autoconnect - Wenn das Signal von einem anderen Thread als das empfangende Objekt ausgesendet wird, wird das Signal Warteschlange gestellt, verhalten, als Qt :: QueuedConnection. Ansonsten wird der Slot direkt aufgerufen, verhält sich wie Qt :: DirectConnection. Die Art der Verbindung wird bestimmt, wenn das Signal ausgegeben wird. [Hervorhebungen hinzugefügt]

Also, solange das type Argument connect zu QtCore.Qt.AutoConnection gesetzt ist (das ist der Standard), sollte Qt sicherzustellen, dass Signale in der entsprechenden Art und Weise emittiert werden.

Das Problem mit dem Beispielcode ist wahrscheinlicher mit dem Schlitz als das Signal zu sein.Die Python-Methode, die das Signal verbunden muss wahrscheinlich als Qt-Slot markiert werden, mit dem pyqtSlot decorator:

from QtCore import pyqtSlot 

class Scanner(QObject): 

    @pyqtSlot() 
    def scan(self): 
     scan_value(start, stop, step) 
     progress.setValue(100) 

EDIT:

Es soll klargestellt werden, dass es nur in relativ neuen Versionen Qt, dass die Art der Verbindung bestimmt wird, wenn das Signal ausgesendet wird. Dieses Verhalten wurde (zusammen mit einigen anderen Änderungen in Qt Multithreading-Unterstützung) mit Version 4.4 eingeführt.

Es könnte sich auch lohnen, das PyQt-spezifische Problem weiter zu vertiefen. In PyQt kann ein Signal mit einem Qt-Slot, einem anderen Signal oder einem beliebigen aufrufbaren Python verbunden werden. Im letzteren Fall wird intern ein Proxy-Objekt erstellt, das die Python-Callable umschließt und den Slot bereitstellt, der für den Qt-Signal-/Slot-Mechanismus erforderlich ist.

Es ist dieses Proxy-Objekt, das die Ursache des Problems ist. Sobald die Proxy erstellt wird, wird PyQt einfach tun:

if (rx_qobj) 
     proxy->moveToThread(rx_qobj->thread()); 

was in Ordnung ist, wenn die Verbindung nach Objekt des Empfangs gemacht hat zu ihrem Gewinde bewegt worden ist; aber wenn es vor gemacht wird, wird der Proxy im Hauptthread bleiben.

Die Verwendung der @pyqtSlot Dekorator vermeidet dieses Problem vollständig, da es einen Qt-Steckplatz direkter erstellt und überhaupt kein Proxy-Objekt verwendet.

Schließlich sollte auch beachtet werden, dass dieses Problem derzeit PySide nicht betrifft.

-1

Dies hat mit den Verbindungstypen von Qt zu tun.

http://pyqt.sourceforge.net/Docs/PyQt5/signals_slots.html#connect

http://qt-project.org/doc/qt-4.8/qt.html#ConnectionType-enum

Fall In beiden Objekte in dem gleichen Thread leben, ist ein Standardverbindungstyp hergestellt, was in einem normalen Funktionsaufruf führt. In diesem Fall findet die zeitaufwändige Operation im GUI-Thread statt und die Schnittstellenblöcke blockieren.

Wenn der Verbindungstyp eine Message-Passing-Style-Verbindung ist, wird das Signal mit einer Nachricht ausgegeben, die im anderen Thread behandelt wird. Der GUI-Thread kann nun die Benutzeroberfläche aktualisieren.

Wenn Sie in der Verbindungsfunktion den Verbindungstyp nicht angeben, wird der Typ automatisch erkannt.

+0

Dies scheint nicht die Besorgnis anzugehen, oder? Selbst, Sie selbst bemerken, dass der Standard automatisch ist (was funktionieren sollte). Siehe die andere Antwort für Details. – lpapp

Verwandte Themen