PyQt:如何将停止信号发送到对象是 运行 条件 while 循环的线程中?
PyQt: How to send a stop signal into a thread where an object is running a conditioned while loop?
我正在做一些多线程。我有一个工人 class 和 work
方法,我将其发送到一个单独的 QThread
。 work
方法内部有一个有条件的 while 循环。我希望能够向工作对象发送信号以停止它(将 _running
条件更改为 false)。这将导致 while 循环退出,并从工作对象(连接到工作线程的退出槽)发送完成信号。
错误条件通过信号发送到工作对象,但从未收到,我认为这是因为 while 循环阻塞了其线程的事件循环。即使我将 QCoreApplication.processEvents()
放在 while 循环中,也没有任何反应。问题出在哪里?为什么不处理信号? (注意 Worker 停止槽中的 print 语句永远不会执行 - 但奇怪的是,线程似乎以错误的方式停止)。
代码如下:
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)
跨线程 signal/slot 连接需要接收方对象线程中的 运行 事件循环。
在你的情况下,第二个线程中有一个事件循环,它是 运行,但它始终在执行你的 work
方法,从不从那里执行 returns。
所以所有槽调用事件都卡在事件循环的事件队列中。
如果你想解决这个问题,就像你尝试使用 QCoreApplication.processEvents
一样,你可以尝试获取线程的 eventDispatcher
并调用它的 processEvent
.
如果你只需要结束 worker,你可以调用线程的 requestInteruption
而不是检查 self._running
你检查线程的 isInterruptionRequested
.
您的示例中存在两个主要问题:
首先,您发出一个信号来停止 worker,但由于该信号是跨线程的,因此它将被发布到接收者的事件队列中。但是,worker 运行 是一个阻塞的 while 循环,因此无法处理挂起的事件。有几种方法可以解决这个问题,但最简单的方法可能是直接调用 worker 的 stop
方法而不是使用信号。
其次,您没有明确地运行主线程中的事件循环,因此无法对从 worker 发送的跨线程信号进行排队。更重要的是,在用户按下一个键后,也没有什么可以阻止程序退出——所以客户端和工作人员将立即被垃圾收集。
下面是您的示例的重写版本,修复了所有问题:
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('\nquit 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\n')
client.toggle(False)
QTimer.singleShot(10, start)
sys.exit(app.exec_())
我正在做一些多线程。我有一个工人 class 和 work
方法,我将其发送到一个单独的 QThread
。 work
方法内部有一个有条件的 while 循环。我希望能够向工作对象发送信号以停止它(将 _running
条件更改为 false)。这将导致 while 循环退出,并从工作对象(连接到工作线程的退出槽)发送完成信号。
错误条件通过信号发送到工作对象,但从未收到,我认为这是因为 while 循环阻塞了其线程的事件循环。即使我将 QCoreApplication.processEvents()
放在 while 循环中,也没有任何反应。问题出在哪里?为什么不处理信号? (注意 Worker 停止槽中的 print 语句永远不会执行 - 但奇怪的是,线程似乎以错误的方式停止)。
代码如下:
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)
跨线程 signal/slot 连接需要接收方对象线程中的 运行 事件循环。
在你的情况下,第二个线程中有一个事件循环,它是 运行,但它始终在执行你的 work
方法,从不从那里执行 returns。
所以所有槽调用事件都卡在事件循环的事件队列中。
如果你想解决这个问题,就像你尝试使用 QCoreApplication.processEvents
一样,你可以尝试获取线程的 eventDispatcher
并调用它的 processEvent
.
如果你只需要结束 worker,你可以调用线程的 requestInteruption
而不是检查 self._running
你检查线程的 isInterruptionRequested
.
您的示例中存在两个主要问题:
首先,您发出一个信号来停止 worker,但由于该信号是跨线程的,因此它将被发布到接收者的事件队列中。但是,worker 运行 是一个阻塞的 while 循环,因此无法处理挂起的事件。有几种方法可以解决这个问题,但最简单的方法可能是直接调用 worker 的 stop
方法而不是使用信号。
其次,您没有明确地运行主线程中的事件循环,因此无法对从 worker 发送的跨线程信号进行排队。更重要的是,在用户按下一个键后,也没有什么可以阻止程序退出——所以客户端和工作人员将立即被垃圾收集。
下面是您的示例的重写版本,修复了所有问题:
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('\nquit 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\n')
client.toggle(False)
QTimer.singleShot(10, start)
sys.exit(app.exec_())