2012-03-15 22 views
6

Postanowiłem dodać GUI do jednego z moich skryptów. Skrypt to prosty skrobaczka do stron internetowych. Postanowiłem użyć wątku roboczego, ponieważ pobieranie i parsowanie danych może trochę potrwać. Postanowiłem użyć PySide, ale moja znajomość Qt w ogóle jest dość ograniczona.PySide czekać na sygnał z głównego wątku w wątku roboczy

Ponieważ skrypt powinien czekać na dane wprowadzone przez użytkownika po napotkaniu captcha, zdecydowałem, że powinien poczekać, aż QLineEdit odpali returnPressed, a następnie wyśle ​​jego zawartość do wątku roboczego, aby mógł wysłać do sprawdzenia poprawności. To powinno być lepsze niż zajęte - oczekiwanie na naciśnięcie klawisza powrotu.

Wygląda na to, że czekanie na sygnał nie jest tak proste, jak myślałem, a po pewnym czasie natrafiłem na kilka rozwiązań podobnych do this. Sygnalizacja między wątkami i lokalna pętla zdarzeń w wątku roboczym sprawiają jednak, że moje rozwiązanie jest nieco bardziej skomplikowane.

Po kilku sekundach majsterkowania nadal nie będzie działać.

Co ma się wydarzyć:

  • Pobierz dane aż skieruję do captcha i wprowadź pętli
  • Pobierz captcha i wyświetlić go do użytkownika, start QEventLoop wywołując self.loop.exec_()
  • Wyjdź QEventLoop wywołując loop.quit() w gnieździe wątków roboczych połączonym przez self.line_edit.returnPressed.connect(self.worker.stop_waiting) w klasie main_window
  • Sprawdź poprawność captcha i pętli, jeśli validati na nie, w przeciwnym razie powtórzyć ostatni adres URL, który powinien być teraz do pobrania, a następnie przejść do następnego url

co się dzieje:

  • ... patrz wyżej ...

  • Opuszczanie QEventLoop nie działa. self.loop.isRunning() zwraca False po wywołaniu jej exit(). self.isRunning zwraca True, ponieważ wątek nie zdawał się ginąć w dziwnych okolicznościach. Nadal wątek zatrzymuje się na linii self.loop.exec_(). W związku z tym wątek utknął podczas wykonywania pętli zdarzeń, mimo że pętla zdarzeń mówi mi, że nie jest już uruchomiona.

  • Interfejs GUI odpowiada tak samo, jak otwory klasy wątków roboczych. Widzę tekst wysyłany do wątku roboczego, status pętli zdarzeń i samego wątku, ale nic po wykonaniu powyższej linii.

Kod jest nieco zawiłe, takich jak dodam trochę pseudo-kodu-python-mix pomijając nieistotne:

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

Odpowiedz

2

Gniazdo jest wykonywane wewnątrz gwintu, który utworzył QThread, a nie w gwint, który kontroluje QThread.

Trzeba przenieść QObject do wątku i podłączyć do gniazda sygnału, a szczelina będą realizowane wewnątrz wątku:

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

Rzeczywiście, które rozwiązać mój problem. Dzięki. –

3

co opisujesz wygląda ideał dla QWaitCondition.

prosty przykład

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_()) 
Powiązane problemy