Domanda

Ho deciso di aggiungere una GUI a uno dei miei script. Lo script è un semplice raschietto web. Ho deciso di utilizzare un filo del lavoratore come scaricare e analizzare i dati può richiedere un po 'di tempo. Ho deciso di usare la Pyside, ma la mia conoscenza del QT in generale è piuttosto limitata.

Poiché lo script dovrebbe attendere che l'immissione dell'utente arrivi attraverso un CAPTCHA ho deciso che dovrebbe attendere fino a quando un QLineEdit incendi returnPressed e quindi inviano il suo contenuto al filo del lavoratore in modo che possa inviarlo per la convalida. Questo dovrebbe essere migliore che in attesa di essere premuto il tasto di ritorno da premere.

Sembra che in attesa di un segnale non sia dritto come pensavo che sarebbe stato e dopo aver cercato un po 'ho trovato diverse soluzioni simili a questo . Segnalazione su threads e un ciclo di eventi locali nel thread del lavoratore rendono la mia soluzione un po 'più complicata però.

Dopo aver toccato con esso per diverse ore, ancora non funzionerà.

Cosa dovrebbe accadere:

    .
  • Scarica i dati fino a quando si fa riferimento a CAPTCHA e inserisci un loop
  • Scarica il captcha e visualizzalo all'utente, avviare QEventLoop chiamando self.loop.exec_()
  • Esci dall'uscita QEventLoop chiamando loop.quit() in uno slot dei fili di lavorazione che è collegato tramite self.line_edit.returnPressed.connect(self.worker.stop_waiting) nella classe main_window
  • Convalida Captcha e Loop Se la convalida fallisce, altrimenti riprova l'URL dell'ultimo che dovrebbe essere scaricabile ora, quindi andare avanti con l'URL successivo

Cosa succede:

    .
  • ... Vedi sopra ...

  • Exhiting QEventLoop non funziona. self.loop.isRunning() restituisce False dopo aver chiamato il suo exit(). self.isRunning restituisce True, in quanto tale il thread non sembrava morire in circostanze strane. Ancora il thread si ferma nella linea self.loop.exec_(). Come tale il filo è bloccato eseguendo il ciclo dell'evento anche se il ciclo dell'evento mi dice che non è più correre.

  • La GUI risponde come fanno le slot della classe del filo lavoratore. Posso vedere il testo Apeing inviare al filo del lavoratore, lo stato del loop evento e il filo stesso, ma nulla dopo la linea sopra menzionata viene eseguita.

Il codice è un po 'contorto, in quanto tale aggiungo un po' di pseudo-code-python-mix lasciando fuori il non importante:

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

È stato utile?

Soluzione

Lo slot viene eseguito all'interno del filo che ha creato QThread e non nella filettatura che il QThread controlla.

È necessario spostare un QObject sul filo e collegare il suo slot al segnale e che lo slot verrà eseguito all'interno del filo:

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

Altri suggerimenti

Cosa stai descrivendo Sembra ideale per QWaitCondition .

Esempio semplice:

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top