Pregunta

He decidido añadir una interfaz gráfica de usuario para uno de mis guiones.El guión es una simple web de raspador.Decidí usar un subproceso de trabajo como descargar y analizar los datos puede tardar un rato.Decidí usar PySide, pero mi conocimiento de Qt en general es bastante limitada.

Como la secuencia de comandos se supone que es para esperar la entrada del usuario al que viene a través de un captcha decidí que debería esperar hasta que un QLineEdit incendios returnPressed y, a continuación, enviar su contenido al subproceso de trabajo de modo que se puede enviar para su validación.Que debe ser mejor que la de ocupado-esperar el regreso tecla que debe pulsar.

Parece que la espera de una señal no es tan sencillo como pensé que sería, y después de buscar por un tiempo me encontré con varias soluciones similares a este.La señalización a través de los subprocesos y un evento local loop en el subproceso de hacer mi solución es un poco más complicado, sin embargo.

Después de juguetear con ella durante varias horas aún no funcionan.

¿Qué se supone que debe ocurrir:

  • Descarga de datos de hasta refirió a captcha y entrar en un bucle
  • Descargar captcha y mostrar al usuario, inicio QEventLoop llamando self.loop.exec_()
  • Salida QEventLoop llamando loop.quit() en un subprocesos de trabajo de la ranura que está conectado a través de self.line_edit.returnPressed.connect(self.worker.stop_waiting) en el main_window clase
  • Validar el captcha y bucle si la validación falla, de lo contrario, vuelva a intentar la última url que debe ser descargable, ahora, luego, continúe con la siguiente url

Lo que sucede:

  • ...ver más arriba...

  • Salir QEventLoop no funciona. self.loop.isRunning() devuelve False después de llamar a su exit(). self.isRunning devuelve True, por lo que el hilo no parecen morir bajo impar circunstancias.Sigue el hilo se detiene en el self.loop.exec_() de la línea.Como tal, el hilo está atascado de ejecutar el bucle de eventos aunque el bucle de eventos me dice que no se está ejecutando ya.

  • La interfaz gráfica de usuario responde como hacer las ranuras del subproceso de trabajo de la clase.Puedo ver el texto en muy buen estado enviar para el subproceso de trabajo, el estado del bucle de eventos y el hilo en sí mismo, pero nada después de la mencionada línea se ejecuta.

El código es un poco complicada, tales como puedo agregar un poco de pseudo-código-python-mix dejando fuera lo que no es 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_()
¿Fue útil?

Solución

La ranura se ejecuta dentro del subproceso que creó el QThread, y no en el hilo que la QThread controles.

Usted necesita para mover un QObject para el hilo y conectar su ranura a la señal, y que la ranura será ejecutado en el interior de la rosca:

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

Otros consejos

Lo que describes parece ideal para QWaitCondition.

Ejemplo sencillo:

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_())
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top