Frage

Ich habe beschlossen, einem meiner Skripte eine GUI hinzuzufügen.Das Skript ist ein einfacher Web-Scraper.Ich habe mich für die Verwendung eines Arbeitsthreads entschieden, da das Herunterladen und Parsen der Daten eine Weile dauern kann.Ich habe mich für PySide entschieden, aber meine Kenntnisse über Qt im Allgemeinen sind recht begrenzt.

Da das Skript auf Benutzereingaben warten soll, wenn es auf ein Captcha stößt, habe ich beschlossen, dass es warten sollte, bis ein QLineEdit Brände returnPressed und dann den Inhalt an den Arbeitsthread senden, damit dieser ihn zur Validierung senden kann.Das sollte besser sein als das Warten auf das Drücken der Eingabetaste.

Es scheint, dass das Warten auf ein Signal nicht so einfach ist, wie ich dachte, und nach einer Weile der Suche bin ich auf mehrere ähnliche Lösungen gestoßen Das.Die Signalisierung über Threads hinweg und eine lokale Ereignisschleife im Worker-Thread machen meine Lösung allerdings etwas komplizierter.

Nach mehrstündigem Herumbasteln funktioniert es immer noch nicht.

Was soll passieren:

  • Laden Sie die Daten herunter, bis sie zum Captcha weitergeleitet werden, und starten Sie eine Schleife
  • Captcha herunterladen und dem Benutzer anzeigen, starten QEventLoop per Anruf self.loop.exec_()
  • Ausfahrt QEventLoop per Anruf loop.quit() in einem Worker-Threads-Steckplatz, der über verbunden ist self.line_edit.returnPressed.connect(self.worker.stop_waiting) im main_window Klasse
  • Validieren Sie das Captcha und wiederholen Sie die Schleife, wenn die Validierung fehlschlägt. Andernfalls versuchen Sie es erneut mit der letzten URL, die jetzt herunterladbar sein sollte, und fahren Sie dann mit der nächsten URL fort

Was geschieht:

  • ...siehe oben...

  • Verlassen QEventLoop funktioniert nicht. self.loop.isRunning() kehrt zurück False nach dem Aufruf exit(). self.isRunning kehrt zurück True, daher schien der Thread unter seltsamen Umständen nicht abzusterben.Trotzdem bleibt der Thread stehen self.loop.exec_() Linie.Daher bleibt der Thread bei der Ausführung der Ereignisschleife hängen, obwohl die Ereignisschleife mir mitteilt, dass er nicht mehr ausgeführt wird.

  • Die GUI reagiert ebenso wie die Slots der Worker-Thread-Klasse.Ich kann den Text sehen, der an den Arbeitsthread gesendet wird, den Status der Ereignisschleife und den Thread selbst, aber nichts, nachdem die oben genannte Zeile ausgeführt wurde.

Der Code ist etwas kompliziert, deshalb füge ich ein bisschen Pseudo-Code-Python-Mix hinzu und lasse das Unwichtige weg:

class MainWindow(...):
    # couldn't find a way to send the text with the returnPressed signal, so I
    # added a helper signal, seems to work though. Doesn't work in the
    # constructor, might be a PySide bug?
    helper_signal = PySide.QtCore.Signal(str)
    def __init__(self):
        # ...setup...
        self.worker = WorkerThread()
        self.line_edit.returnPressed.connect(self.helper_slot)
        self.helper_signal.connect(self.worker.stop_waiting)

    @PySide.QtCore.Slot()
    def helper_slot(self):
        self.helper_signal.emit(self.line_edit.text())

class WorkerThread(PySide.QtCore.QThread):
    wait_for_input = PySide.QtCore.QEventLoop()

    def run(self):
        # ...download stuff...
        for url in list_of_stuff:
            self.results.append(get(url))

    @PySide.QtCore.Slot(str)
    def stop_waiting(self, text):
        self.solution = text
        # this definitely gets executed upon pressing return
        self.wait_for_input.exit()

    # a wrapper for requests.get to handle captcha
    def get(self, *args, **kwargs):
        result = requests.get(*args, **kwargs)
        while result.history: # redirect means captcha
            # ...parse and extract captcha...
            # ...display captcha to user via not shown signals to main thread...

            # wait until stop_waiting stops this event loop and as such the user
            # has entered something as a solution
            self.wait_for_input.exec_()

            # ...this part never get's executed, unless I remove the event
            # loop...

            post = { # ...whatever data necessary plus solution... }
            # send the solution
            result = requests.post('http://foo.foo/captcha_url'), data=post)
        # no captcha was there, return result
        return result

frame = MainWindow()
frame.show()
frame.worker.start()
app.exec_()
War es hilfreich?

Lösung

Der Slot wird innerhalb des Threads ausgeführt, der den erstellt hat QThread, und nicht in dem Thread, den das QThread Kontrollen.

Sie müssen umziehen QObject zum Thread und verbinde seinen Slot mit dem Signal, und dieser Slot wird innerhalb des Threads ausgeführt:

class SignalReceiver(QtCore.QObject):
    def __init__(self):
        self.eventLoop = QEventLoop(self)             

    @PySide.QtCore.Slot(str)
    def stop_waiting(self, text):                   
        self.text = text
        eventLoop.exit()

    def wait_for_input(self):
        eventLoop.exec()
        return self.text

class MainWindow(...):
     ...
     def __init__(self):
        ...
        self.helper_signal.connect(self.worker.signalReceiver.stop_waiting)

class WorkerThread(PySide.QtCore.QThread): 
    def __init__(self):
        self.signalReceiver = SignalReceiver() 
        # After the following call the slots will be executed in the thread             
        self.signalReceiver.moveToThread(self)    

    def get(self,  *args, **kwargs):
        result = requests.get(*args, **kwargs)
        while result.history:
            ...
            self.result = self.signalReceiver.wait_for_input()   

Andere Tipps

Was Sie beschreiben, sieht ideal aus QWaitCondition.

Einfaches Beispiel:

import sys
from PySide import QtCore, QtGui

waitCondition = QtCore.QWaitCondition()
mutex = QtCore.QMutex()

class Main(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(Main, self).__init__()

        self.text = QtGui.QLineEdit()
        self.text.returnPressed.connect(self.wakeup)

        self.worker = Worker(self)
        self.worker.start()

        self.setCentralWidget(self.text)

    def wakeup(self):
        waitCondition.wakeAll()

class Worker(QtCore.QThread):
    def __init__(self, parent=None):
        super(Worker, self).__init__(parent)

    def run(self):
        print "initial stuff"

        mutex.lock()
        waitCondition.wait(mutex)
        mutex.unlock()

        print "after returnPressed"

if __name__=="__main__":      
    app = QtGui.QApplication(sys.argv)
    m = Main()
    m.show()
    sys.exit(app.exec_())
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top