Pyqt5两个QThread通信使用信号和插槽问题



我正在尝试将pyqtqthread用于多线程程序。

在代码中,有两个不同的worker实例。我尝试使用信号和插槽进行数据共享。但在其中一个qthread完成之前,信号似乎被阻塞了。

这是代码。

from PyQt5.QtCore import QCoreApplication,QThread, QObject, pyqtSignal, pyqtSlot
import time
import sys
class Worker(QObject):
finished = pyqtSignal()
intReady = pyqtSignal(int)
@pyqtSlot()
def work(self):  # A slot takes no params
for i in range(1, 10):
time.sleep(1)
self.intReady.emit(i)
self.finished.emit()

class Worker2(QObject):
finished = pyqtSignal()
intReady = pyqtSignal(int)
@pyqtSlot()
def work(self):  # A slot takes no params
for i in range(1, 10):
time.sleep(1)
self.intReady.emit(i)
self.finished.emit()
@pyqtSlot(int)
def revsignal(self, val):
print("hello rev a signal"+str(val))
def updateLabel(val):
print("updateLable "+str(val))
app = QCoreApplication(sys.argv)
# 1 - create Worker and Thread inside the Form
worker = Worker()  # no parent!
thread = QThread()  # no parent!
worker2 = Worker2()
thread2 = QThread()
worker.intReady.connect(updateLabel)
worker.intReady.connect(worker2.revsignal)
worker.moveToThread(thread)
worker.finished.connect(thread.quit)
thread.started.connect(worker.work)
# self.thread.finished.connect(app.exit)

worker2.moveToThread(thread2)
worker2.finished.connect(thread2.quit)
thread2.started.connect(worker2.work)
thread2.finished.connect(app.exit)

thread2.start()
print("after thread2 start")
thread.start()
print("after thread1 start.")

sys.exit(app.exec_())

输出是

after thread2 start
after thread1 start.
updateLable 1
updateLable 2
updateLable 3
updateLable 4
updateLable 5
updateLable 6
updateLable 7
updateLable 8
updateLable 9
hello rev a signal1
hello rev a signal2
hello rev a signal3
hello rev a signal4
hello rev a signal5
hello rev a signal6
hello rev a signal7
hello rev a signal8
hello rev a signal9
Process finished with exit code 0

我不知道为什么worker2线程不能在work1发出信号时立即接收到信号。直到工件1线程结束。

非常感谢您的帮助!

首先,所有插槽都应该是从QObject继承的类的成员。所以做一个像这样的类

class Manager(QObject):
@pyqtSlot(int)
def updateLabel(self, val):
print("updateLable "+str(val))
manager = Manager()

接下来,根据这里的文档,有一个额外的参数可以传递给connect,以控制跨线程信号处理的行为。你需要换三行。

from PyQt5.QtCore import QCoreApplication, QThread, QObject, pyqtSignal, pyqtSlot, Qt

以获得Qt命名空间,以及

worker.intReady.connect(manager.updateLabel, type=Qt.DirectConnection)
worker.intReady.connect(worker2.revsignal, type=Qt.DirectConnection)

以设置连接类型。

您误解了线程和信号/插槽的工作方式

信号和插槽处理

发出信号时,应运行与其连接的插槽。使用DirectConnection时,这意味着直接调用slot函数,但这只是对象位于同一线程上时的默认设置。当对象位于不同的线程上(或者您使用了QueuedConnection(时,信号会将事件排入队列,以在相应线程的事件队列中运行插槽。这就是你想要的。

线程事件循环

每个线程都是一个单独的执行线程,运行一个事件循环。事件循环调用已经接收到信号的插槽,但它是按顺序调用的,也就是说,它调用一个插槽,然后当该函数返回时,它调用下一个,依此类推

你做错了什么

这是两件事的结合。首先,您在插槽中执行整个逻辑循环,这意味着在循环完成之前不会处理其他事件。其次,您在插槽中调用time.sleep,这将导致整个线程睡眠1秒。

结合起来,这意味着两个线程将首先调用工作槽,在该槽完成之前"忙碌"10秒,然后完成其余的工作。

解决方案

直接连接

正如buck54321所建议的,您可以使用直接连接,但这只是意味着worker1work函数(在thread1上运行(将直接调用worker2.revsignal,仍然在thread1上。

手动处理事件

您可以调用QCoreApplication.processEvents()手动运行(的迭代(事件循环。有时这可能是正确的解决方案,但它很混乱,你仍然会让线程一次休眠一秒钟。

使用定时事件

您可以使用计时器定期调用您的插槽,然后进行工作,而不是循环。这意味着在完成之前不必循环。我已经包含了一个使用QTimer.singleShot的示例。

代码

已清理

这是一个清理后的版本,其行为仍然大致相同:

from PyQt5.QtCore import QCoreApplication,QThread, QObject, pyqtSignal, pyqtSlot
import time
import sys
class Worker(QObject):
finished = pyqtSignal()
intReady = pyqtSignal(int, int)
def __init__(self, number):
super().__init__()
self.number = number
@pyqtSlot()
def work(self):
for i in range(1, 10):
time.sleep(1)
self.intReady.emit(self.number, i)
self.finished.emit()
@pyqtSlot(int)
def revsignal(self, val):
print("hello rev a signal"+str(val))
class Label(QObject):
@pyqtSlot(int, int)
def updateLabel(self, source, val):
print(f"updateLabel ({source}): {val}")
class Exiter(QObject):
done = pyqtSignal()
def __init__(self, num_threads):
super().__init__()
self.live_threads = num_threads
@pyqtSlot()
def finished(self):
self.live_threads -= 1
if self.live_threads <= 0:
self.done.emit()
app = QCoreApplication(sys.argv)
# 1 - create Worker and Thread inside the Form
worker1 = Worker(1)  # no parent!
thread1 = QThread()  # no parent!
worker2 = Worker(2)
thread2 = QThread()
label = Label()
exiter = Exiter(2) # Waiting for 2 threads
worker1.intReady.connect(label.updateLabel)
worker1.intReady.connect(worker2.revsignal)
worker1.moveToThread(thread1)
worker1.finished.connect(thread1.quit)
thread1.started.connect(worker1.work)
thread1.finished.connect(exiter.finished)
worker2.intReady.connect(label.updateLabel)
worker2.moveToThread(thread2)
worker2.finished.connect(thread2.quit)
thread2.started.connect(worker2.work)
thread2.finished.connect(exiter.finished)
exiter.done.connect(app.exit)

thread2.start()
print("after thread2 start")
thread1.start()
print("after thread1 start.")

sys.exit(app.exec_())

哈克

此版本强制在睡眠前进行事件处理,至少可以处理其他事件,但仍会导致线程睡眠。

from PyQt5.QtCore import QCoreApplication,QThread, QObject, pyqtSignal, pyqtSlot
import time
import sys
class Worker(QObject):
finished = pyqtSignal()
intReady = pyqtSignal(int, int)
def __init__(self, number):
super().__init__()
self.number = number
@pyqtSlot()
def work(self):
for i in range(1, 10):
QCoreApplication.processEvents()
time.sleep(1)
self.intReady.emit(self.number, i)
self.finished.emit()
@pyqtSlot(int)
def revsignal(self, val):
print("hello rev a signal"+str(val))
class Label(QObject):
@pyqtSlot(int, int)
def updateLabel(self, source, val):
print(f"updateLabel ({source}): {val}")
class Exiter(QObject):
done = pyqtSignal()
def __init__(self, num_threads):
super().__init__()
self.live_threads = num_threads
@pyqtSlot()
def finished(self):
self.live_threads -= 1
if self.live_threads <= 0:
self.done.emit()
app = QCoreApplication(sys.argv)
# 1 - create Worker and Thread inside the Form
worker1 = Worker(1)  # no parent!
thread1 = QThread()  # no parent!
worker2 = Worker(2)
thread2 = QThread()
label = Label()
exiter = Exiter(2) # Waiting for 2 threads
worker1.intReady.connect(label.updateLabel)
worker1.intReady.connect(worker2.revsignal)
worker1.moveToThread(thread1)
worker1.finished.connect(thread1.quit)
thread1.started.connect(worker1.work)
thread1.finished.connect(exiter.finished)
worker2.intReady.connect(label.updateLabel)
worker2.moveToThread(thread2)
worker2.finished.connect(thread2.quit)
thread2.started.connect(worker2.work)
thread2.finished.connect(exiter.finished)
exiter.done.connect(app.exit)

thread2.start()
print("after thread2 start")
thread1.start()
print("after thread1 start.")

sys.exit(app.exec_())

定时事件

在这里,逻辑被重写为使用定时事件,而不是睡眠。

from PyQt5.QtCore import QCoreApplication, QThread, QTimer, QObject, pyqtSignal, pyqtSlot
import sys
class Worker(QObject):
finished = pyqtSignal()
intReady = pyqtSignal(int, int)
def __init__(self, number):
super().__init__()
self.number = number
self.to_emit = iter(range(1, 10))
@pyqtSlot()
def work(self):
try:
value = next(self.to_emit)
self.intReady.emit(self.number, value)
QTimer.singleShot(1000, self.work)
except StopIteration:
self.finished.emit()
@pyqtSlot(int)
def revsignal(self, val):
print("hello rev a signal"+str(val))
class Label(QObject):
@pyqtSlot(int, int)
def updateLabel(self, source, val):
print(f"updateLabel ({source}): {val}")
class Exiter(QObject):
done = pyqtSignal()
def __init__(self, num_threads):
super().__init__()
self.live_threads = num_threads
@pyqtSlot()
def finished(self):
self.live_threads -= 1
if self.live_threads <= 0:
self.done.emit()
app = QCoreApplication(sys.argv)
# 1 - create Worker and Thread inside the Form
worker1 = Worker(1)  # no parent!
thread1 = QThread()  # no parent!
worker2 = Worker(2)
thread2 = QThread()
label = Label()
exiter = Exiter(2) # Waiting for 2 threads
worker1.intReady.connect(label.updateLabel)
worker1.intReady.connect(worker2.revsignal)
worker1.moveToThread(thread1)
worker1.finished.connect(thread1.quit)
thread1.started.connect(worker1.work)
thread1.finished.connect(exiter.finished)
worker2.intReady.connect(label.updateLabel)
worker2.moveToThread(thread2)
worker2.finished.connect(thread2.quit)
thread2.started.connect(worker2.work)
thread2.finished.connect(exiter.finished)
exiter.done.connect(app.exit)

thread2.start()
print("after thread2 start")
thread1.start()
print("after thread1 start.")

sys.exit(app.exec_())

最新更新