简体   繁体   English

PySide等待来自工作线程中主线程的信号

[英]PySide wait for signal from main thread in a worker thread

I decided to add a GUI to one of my scripts. 我决定在我的一个脚本中添加一个GUI。 The script is a simple web scraper. 该脚本是一个简单的Web scraper。 I decided to use a worker thread as downloading and parsing the data can take a while. 我决定使用工作线程作为下载和解析数据可能需要一段时间。 I decided to use PySide, but my knowledge of Qt in general is quite limited. 我决定使用PySide,但我对Qt的了解非常有限。

As the script is supposed to wait for user input upon coming across a captcha I decided it should wait until a QLineEdit fires returnPressed and then send it's content to the worker thread so it can send it for validation. 由于脚本应该在遇到验证码时等待用户输入,我决定它应该等到QLineEdit触发returnPressed ,然后将其内容发送到工作线程,以便它可以发送它进行验证。 That should be better than busy-waiting for the return key to be pressed. 这应该比忙碌更好 - 等待按下返回键。

It seems that waiting for a signal isn't as straight forward as I thought it would be and after searching for a while I came across several solutions similar to this . 似乎等待信号并不像我想象的那样直截了当,在搜索了一段时间后,我遇到了几个类似于此的解决方案。 Signaling across threads and a local event loop in the worker thread make my solution a bit more complicated though. 跨线程的信令和工作线程中的本地事件循环使我的解决方案有点复杂。

After tinkering with it for several hours it still won't work. 经过几个小时的修补后,它仍然无法正常工作。

What is supposed to happen: 应该发生什么:

  • Download data until refered to captcha and enter a loop 下载数据,直到参考验证码并进入循环
  • Download captcha and display it to the user, start QEventLoop by calling self.loop.exec_() 下载验证码并将其显示给用户,通过调用self.loop.exec_()启动QEventLoop
  • Exit QEventLoop by calling loop.quit() in a worker threads slot which is connected via self.line_edit.returnPressed.connect(self.worker.stop_waiting) in the main_window class 通过在工作线程槽中调用loop.quit()来退出QEventLoop ,该槽通过main_window类中的self.line_edit.returnPressed.connect(self.worker.stop_waiting)连接
  • Validate captcha and loop if validation fails, otherwise retry the last url which should be downloadable now, then move on with the next url 验证验证失败时验证验证码和循环,否则重试现在应该可下载的最后一个URL,然后继续下一个URL

What happens: 怎么了:

  • ...see above... ...往上看...

  • Exiting QEventLoop doesn't work. 退出QEventLoop不起作用。 self.loop.isRunning() returns False after calling its exit() . self.loop.isRunning()在调用exit()后返回False self.isRunning returns True , as such the thread didn't seem to die under odd circumstances. self.isRunning返回True ,因此线程在奇怪的情况下似乎没有死。 Still the thread halts at the self.loop.exec_() line. 线程仍在self.loop.exec_()行停止。 As such the thread is stuck executing the event loop even though the event loop tells me it is not running anymore. 因此,即使事件循环告诉我它不再运行,线程也会执行事件循环。

  • The GUI responds as do the slots of the worker thread class. GUI响应工作线程类的插槽。 I can see the text beeing send to the worker thread, the status of the event loop and the thread itself, but nothing after the above mentioned line gets executed. 我可以看到文本beeing发送到工作线程,事件循环的状态和线程本身,但在上面提到的行执行后没有任何内容。

The code is a bit convoluted, as such I add a bit of pseudo-code-python-mix leaving out the unimportant: 代码有点复杂,因此我添加了一些伪代码-python-mix而忽略了不重要的:

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

What you are describing looks ideal for QWaitCondition . 您所描述的内容看起来非常适合QWaitCondition

Simple example: 简单的例子:

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

The slot is executed inside the thread which created the QThread , and not in the thread that the QThread controls. 插槽在创建QThread的线程内执行,而不是在QThread控制的线程中执行。

You need to move a QObject to the thread and connect its slot to the signal, and that slot will be executed inside the thread: 您需要将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()   

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM