2012-03-15 14 views
6

Ho deciso di aggiungere una GUI a uno dei miei script. Lo script è un semplice raschietto web. Ho deciso di utilizzare un thread di lavoro in quanto il download e l'analisi dei dati possono richiedere del tempo. Ho deciso di usare PySide, ma la mia conoscenza di Qt in generale è piuttosto limitata.PySide attende il segnale dal thread principale in un thread di lavoro

Poiché lo script deve attendere l'input dell'utente dopo aver trovato un captcha, ho deciso che doveva attendere fino a quando uno QLineEdit genera returnPressed e quindi invia il suo contenuto al thread di lavoro in modo che possa inviarlo per la convalida. Dovrebbe essere migliore di quello occupato, in attesa che venga premuto il tasto Invio.

Sembra che attendere un segnale non sia così semplice come pensavo sarebbe stato e dopo aver cercato un po 'mi sono imbattuto in diverse soluzioni simili a this. Segnalare attraverso i thread e un ciclo di eventi locale nel thread di lavoro rendono la mia soluzione un po 'più complicata.

Dopo averlo armeggiato per diverse ore, non funzionerà ancora.

quello che dovrebbe accadere:

  • Scarico dati fino a quando riferito a captcha e inserire un ciclo
  • scaricare CAPTCHA e visualizzarlo per l'utente, avviare QEventLoop chiamando self.loop.exec_()
  • Exit QEventLoop chiamando loop.quit() in uno slot di thread di lavoro che è collegato tramite self.line_edit.returnPressed.connect(self.worker.stop_waiting) nella classe main_window
  • Convalidare captcha e loop se validati il fallisce, altrimenti ripetere l'ultimo URL che dovrebbe essere scaricabile ora, poi andare avanti con l'URL successivo

Cosa succede:

  • ... vedi sopra ...

  • Chiusura QEventLoop non funziona. self.loop.isRunning() restituisce False dopo aver chiamato il numero exit(). self.isRunning restituisce True, in quanto tale thread non sembra morire in circostanze strane. Il thread si arresta ancora sulla riga self.loop.exec_(). Come tale, il thread è bloccato nell'esecuzione del ciclo degli eventi anche se il ciclo degli eventi mi dice che non è più in esecuzione.

  • La GUI risponde come gli slot della classe del thread di lavoro. Riesco a vedere il testo inviato al thread di lavoro, lo stato del ciclo degli eventi e il thread stesso, ma nulla dopo l'esecuzione della riga sopra menzionata.

Il codice è un po 'contorto, in quanto tale, aggiungo un po' di pseudo-codice-python-mix tralasciando il poco 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_() 

risposta

2

Lo slot è eseguito all'interno del thread che ha creato il QThread, e non nel filo che le QThread controlli.

è necessario spostare un QObject al filo e collegare slot al segnale, e quello slot verranno eseguiti 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() 
+0

Infatti, che ha risolto il problema. Grazie. –

3

ciò che si sta descrivendo sguardi ideale per QWaitCondition.

semplice esempio:

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