PyQt:如何将停止信号发送到对象正在运行有条件的 while 循环的线程? [英] PyQt: How to send a stop signal into a thread where an object is running a conditioned while loop?

查看:28
本文介绍了PyQt:如何将停止信号发送到对象正在运行有条件的 while 循环的线程?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在做一些多线程.我有一个带有 work 方法的工作类,我将其发送到单独的 QThread 中.work 方法内部有一个有条件的 while 循环.我希望能够向工作对象发送信号以停止它(将 _running 条件更改为 false).这将导致 while 循环退出,并从工作对象(连接到工作线程的退出槽)发送完成信号.

I'm doing some multi-threading. I have a worker class with a work method, which I send into a separate QThread. The work method has a conditioned while loop inside. I want to be able to send a signal to the worker object to stop it (changing the _running condition to false). This will cause the while loop to exit, and a finished signal to be sent from the worker object (which is connected to the quit slot of the worker's thread).

错误条件通过信号发送到工作对象,但从未收到,我认为这是因为 while 循环阻塞了其线程的事件循环.即使我将 QCoreApplication.processEvents() 放在 while 循环中,也没有任何反应.问题出在哪儿?为什么不处理信号?(请注意,Worker 上停止槽中的打印语句从未执行过 - 但奇怪的是,该线程似乎以错误的方式停止).

The false condition is sent to the worker object via a signal, but it is never received, which I believe is because the while loop blocks the event-loop of its thread. Even if I put QCoreApplication.processEvents() inside the while loop, nothing happens. Where is the problem? Why isn't the signal processed? (Notice that the print statement in the stop slot on the Worker is never executed - but the weird thing is, the thread does seem to stop in a wrong way).

代码如下:

import time, sys
from PyQt4.QtCore  import *
from PyQt4.QtGui import *

class Worker(QObject):
    sgnFinished = pyqtSignal()

    def __init__(self, parent):
        QObject.__init__(self, parent)

        self._running = True

    @pyqtSlot()
    def stop():
        print 'stop signal received, switching while loop condition to false'
        self._running = False

    @pyqtSlot()    
    def work(self):
        while self._running:                 #this blocks the thread, if changed to an if clause, thread finishes as expected!
            QCoreApplication.processEvents() #this doesn't help!
            time.sleep(0.1)
            print 'doing work...'

        #do some cleanup here, then signal the worker is done
        self.sgnFinished.emit()


class Client(QObject):
    sgnStop = pyqtSignal()

    def __init__(self, parent):
        QObject.__init__(self, parent)

        self._thread = None
        self._worker = None

    def toggle(self, enable):
        if enable:
            if not self._thread:
                self._thread = QThread()

            self._worker = Worker(None)
            self._worker.moveToThread(self._thread)

            self._worker.sgnFinished.connect(self.on_worker_done)
            self.sgnStop.connect(self._worker.stop)

            self._thread.started.connect(self._worker.work)
            self._thread.start()
        else:
            print 'sending stop signal to the worker object'
            self.sgnStop.emit() #send a queuedconnection type signal to the worker, because its in another thread

    @pyqtSlot() 
    def on_worker_done(self):
        print 'workers job was interrupted manually'
        self._thread.quit()
        #self._thread.wait() not sure this is neccessary

if __name__ == '__main__':
    app = QCoreApplication(sys.argv)

    client = Client(None)
    client.toggle(True)
    raw_input('Press something')
    client.toggle(False)

推荐答案

您的示例中有两个主要问题:

There are two main problems in your example:

首先,您发出一个信号来停止工作进程,但由于该信号是跨线程的,它将被发布到接收者的事件队列中.但是,worker 正在运行一个阻塞的 while 循环,因此无法处理挂起的事件.有几种方法可以解决这个问题,但最简单的方法可能是直接调用 worker 的 stop 方法,而不是使用信号.

Firstly, you are emitting a signal to stop the worker, but since the signal is cross-thread, it will be posted in the receiver's event-queue. However, the worker is running a blocking while-loop, so pending events cannot be processed. There are a few ways to work around this, but probably the simplest is to simply call the worker's stop method directly instead of using a signal.

其次,您没有在主线程中显式运行事件循环,因此无法对从工作线程发送的跨线程信号进行排队.更重要的是,在用户按下一个键后也没有什么可以阻止程序退出 - 因此客户端和工作器将立即被垃圾收集.

Secondly, you are not explicitly running an event-loop in the main thread, so cross-thread signals sent from the worker cannot be queued. More importantly, though, there is also nothing to stop the program exiting after the user presses a key - so the client and worker will be immediately garbage-collected.

以下是您示例的重写版本,它解决了所有问题:

Below is a re-written version of your example which fixes all the issues:

import time, sys
from PyQt4.QtCore  import *
from PyQt4.QtGui import *

class Worker(QObject):
    sgnFinished = pyqtSignal()

    def __init__(self, parent):
        QObject.__init__(self, parent)
        self._mutex = QMutex()
        self._running = True

    @pyqtSlot()
    def stop(self):
        print 'switching while loop condition to false'
        self._mutex.lock()
        self._running = False
        self._mutex.unlock()

    def running(self):
        try:
            self._mutex.lock()
            return self._running
        finally:
            self._mutex.unlock()

    @pyqtSlot()
    def work(self):
        while self.running():
            time.sleep(0.1)
            print 'doing work...'
        self.sgnFinished.emit()

class Client(QObject):
    def __init__(self, parent):
        QObject.__init__(self, parent)
        self._thread = None
        self._worker = None

    def toggle(self, enable):
        if enable:
            if not self._thread:
                self._thread = QThread()

            self._worker = Worker(None)
            self._worker.moveToThread(self._thread)
            self._worker.sgnFinished.connect(self.on_worker_done)

            self._thread.started.connect(self._worker.work)
            self._thread.start()
        else:
            print 'stopping the worker object'
            self._worker.stop()

    @pyqtSlot()
    def on_worker_done(self):
        print 'workers job was interrupted manually'
        self._thread.quit()
        self._thread.wait()
        if raw_input('
quit application [Yn]? ') != 'n':
            qApp.quit()

if __name__ == '__main__':

    # prevent some harmless Qt warnings
    pyqtRemoveInputHook()

    app = QCoreApplication(sys.argv)

    client = Client(None)

    def start():
        client.toggle(True)
        raw_input('Press something
')
        client.toggle(False)

    QTimer.singleShot(10, start)

    sys.exit(app.exec_())

这篇关于PyQt:如何将停止信号发送到对象正在运行有条件的 while 循环的线程?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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