2012-03-15 58 views
6

我决定添加一个GUI到我的一个脚本中。该脚本是一个简单的网页刮板。我决定使用工作线程作为下载和解析数据可能需要一段时间。我决定使用PySide,但我对Qt的了解总体上非常有限。PySide等待来自主线程的信号在工作线程中

由于脚本应该在等待用户输入时遇到验证码,我决定应等到QLineEdit激发returnPressed,然后将其内容发送到工作线程,以便它可以发送验证。这应该比忙 - 等待按下返回键更好。

似乎等待信号并不像我想的那样直截了当,搜索了一段时间后,我发现了几个类似于this的解决方案。然而,在线程和本地线程中的本地事件循环之间进行信号传递使我的解决方案变得更加复杂。

经过几个小时的修补后,它仍然无法正常工作。

什么是应该发生:

  • 下载数据,直到refered来验证码,并进入一个循环
  • 下载验证码,并将其显示给用户,开始QEventLoop通过调用调用self.loop.exec_()
  • 退出QEventLooploop.quit()在工作线程插槽中通过self.line_edit.returnPressed.connect(self.worker.stop_waiting)连接main_window
  • 验证验证码和循环如果validati上失败,否则重试上一个网址现在应该是下载的,然后继续下一个URL

会发生什么:

  • ...见上......

  • 退出QEventLoop不起作用。 self.loop.isRunning()在致电exit()后返回Falseself.isRunning返回True,因为在奇怪的情况下线程似乎不会死亡。线程暂停在self.loop.exec_()行。因此,即使事件循环告诉我它不再运行,线程也会卡住执行事件循环。

  • GUI的响应与工作线程类的插槽一样。我可以看到文本beeing发送给工作线程,事件循环的状态和线程本身,但是上面提到的那一行没有被执行。

的代码是有点令人费解,因为这样我增添几分伪代码蟒蛇混合的离开了不重要的:

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

回答

2

该狭槽,其创建QThread线程内执行,而不是在线程的QThread控件。

你需要移动QObject到线程及其插槽连接到信号,该插槽将在线程中执行:

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

事实上,这解决了我的问题。谢谢。 – 2012-03-15 13:30:29

3

你所描述的QWaitCondition看起来理想。

简单的例子:

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