PySide等待来自辅助线程中主线程的信号 [英] PySide wait for signal from main thread in a worker thread

查看:151
本文介绍了PySide等待来自辅助线程中主线程的信号的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我决定将GUI添加到我的一个脚本中.该脚本是一个简单的Web抓取工具.我决定使用辅助线程,因为下载和解析数据可能需要一段时间.我决定使用PySide,但总体而言,我对Qt的了解非常有限.

I decided to add a GUI to one of my scripts. The script is a simple 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.

由于脚本应该在遇到验证码时等待用户输入,所以我决定应该等到QLineEdit触发returnPressed之后再将其内容发送到工作线程,以便可以将其发送以进行验证.这比忙于等待按下返回键要好.

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. 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.

应该发生什么:

  • 下载数据,直到引用验证码并进入循环
  • 下载验证码并将其显示给用户,通过调用self.loop.exec_()
  • 启动QEventLoop
  • 通过在通过main_window类中的self.line_edit.returnPressed.connect(self.worker.stop_waiting)连接的工作线程插槽中调用loop.quit()退出QEventLoop
  • 验证失败时验证验证码并循环,否则重试应立即下载的最后一个URL,然后继续下一个URL
  • 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_()
  • 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
  • Validate captcha and loop if validation fails, otherwise retry the last url which should be downloadable now, then move on with the next url

会发生什么:

  • ...参见上文...

  • ...see above...

退出QEventLoop不起作用. self.loop.isRunning()调用exit()后返回False. self.isRunning返回True,因为这样的线程似乎在特殊情况下不会死掉.线程仍在self.loop.exec_()行处停止.这样,即使事件循环告诉我它不再运行,该线程也被卡在执行事件循环中.

Exiting QEventLoop doesn't work. self.loop.isRunning() returns False after calling its exit(). self.isRunning returns True, as such the thread didn't seem to die under odd circumstances. Still the thread halts at the self.loop.exec_() line. As such the thread is stuck executing the event loop even though the event loop tells me it is not running anymore.

GUI响应工作线程类的插槽.我可以看到文本beeing发送到工作线程,事件循环的状态以及线程本身,但是在执行上述行之后什么都没有.

The GUI responds as do the slots of the worker thread class. 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.

代码有点复杂,因此我添加了一些伪代码-python-mix,而忽略了不重要的事情:

The code is a bit convoluted, as such I add a bit of pseudo-code-python-mix leaving out the unimportant:

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

推荐答案

该插槽在创建QThread的线程内执行,而不是在QThread控制的线程内执行.

The slot is executed inside the thread which created the QThread, and not in the thread that the QThread controls.

您需要将QObject移至线程并将其插槽连接至信号,该插槽将在线程内执行:

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:

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

这篇关于PySide等待来自辅助线程中主线程的信号的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆