2012-03-15 7 views
6

Decidí agregar una GUI a uno de mis scripts. El script es un simple raspador web. Decidí usar un hilo de trabajo como descarga y analizar los datos puede llevar un tiempo. Decidí usar PySide, pero mi conocimiento de Qt en general es bastante limitado.PySide espera de la señal del hilo principal en un hilo de trabajo

Como se supone que el script espera la entrada del usuario al encontrar un captcha decidí que debería esperar hasta que QLineEdit dispare returnPressed y luego envíe su contenido al hilo del trabajador para que pueda enviarlo para su validación. Eso debería ser mejor que ocupado, esperando a que se presione la tecla de retorno.

Parece que esperar una señal no es tan sencillo como pensé que sería y después de buscar por un tiempo encontré varias soluciones similares a this. Sin embargo, la señalización a través de hilos y un bucle de evento local en el hilo de trabajo hacen que mi solución sea un poco más complicada.

Después de jugar con él durante varias horas, todavía no va a funcionar.

Lo que se supone que debe ocurrir:

  • Descarga de datos hasta referido al código de imagen y entrar en un bucle
  • Descargar código de imagen y mostrarla al usuario, iniciar QEventLoop llamando self.loop.exec_()
  • salida QEventLoop llamando loop.quit() en una ranura de subprocesos de trabajador que está conectada a través de self.line_edit.returnPressed.connect(self.worker.stop_waiting) en la clase main_window
  • Validar captcha y bucle si validati en la falla, de lo contrario vuelva a intentar la última dirección URL que debe ser descargable ahora, a continuación, pasar a la siguiente url

Lo que sucede:

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

  • Salir de 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 parece morir bajo circunstancias extrañas. Todavía el hilo se detiene en la línea self.loop.exec_(). Como tal, el hilo está atascado ejecutando el bucle de evento a pesar de que el bucle de evento me dice que ya no se está ejecutando.

  • La GUI responde al igual que las ranuras de la clase de subprocesos de trabajo. Puedo ver el texto enviado al hilo del trabajador, el estado del bucle de evento y el hilo mismo, pero nada después de que se ejecute la línea mencionada.

El código es un poco complicado, como tal, añado un poco de pseudo-código-python-mix dejando de lado la importancia:

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

Respuesta

2

La ranura se ejecuta en el interior del hilo que creó el QThread, y no en el hilo que los QThread controles.

tiene que mover un QObject a la rosca y conectar su ranura de la señal, y que la ranura se ejecutará dentro 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() 
+0

hecho, que resolvió mi problema. Gracias. –

3

Lo que usted describe 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_()) 
Cuestiones relacionadas