关于python:PyQt5“更改QLabel大小时,”无法从另一个线程启动计时器“错误

PyQt5 “Timers cannot be started from another thread” error when changing size of QLabel

我在Python 3.5中遇到了PyQt5的奇怪问题。
我有两个类,FrontEnd(QWidget)TimerThread(Thread)。我在FrontEnd的init函数中定义了许多QLabel,所有这些都正常工作。

显示的是FrontEnd的相关少数功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
def update_ui(self):
    ret, frame = self.cam_capture.read()

    if self.results_pending:
        if not path.isfile('output.jpg'):
            self.results_pending = False
            with open('.out') as content_file:
                content = content_file.readlines()[2:-2]
            system('rm .out')
            self.handle_image_classification(content)

    if self.take_picture:
        cv2.imwrite('output.jpg', frame)
        self.user_prompt.setText('Please wait...')
        system('./classifyimage.py --mean mean.binaryproto --nogpu --labels labels.txt model.caffemodel deploy.prototxt output.jpg > .out && rm output.jpg')
        self.take_picture = False
        self.results_pending = True

    image = QImage(frame, frame.shape[1], frame.shape[0], QImage.Format_RGB888).rgbSwapped()
    pix = QPixmap.fromImage(image)
    self.video_frame.setPixmap(pix)

def update_bar_graph(self, data):
    palette = QPalette()
    palette.setColor(QPalette.Background, Qt.white)
    for i in range(0, 8):
        self.bar_graph_labels[i].setText(str(data[i]) +"%")
        height = int(data[i] * 5)
        self.bar_graph[i].setFixedSize(self.bar_width, height)
        self.bar_graph[i].move(1280 + (i * (self.bar_width + self.bar_spacing)), 640 - height)

def handle_image_classification(self, raw_output):
    data = [None] * 8
    for i in range(0, len(raw_output)):
        raw_output[i] = raw_output[i].strip()
        data[int(raw_output[i][-2]) - 1] = float(raw_output[i][:-10])
    self.update_bar_graph(data)

而整个TimerThread类:

1
2
3
4
5
6
7
8
9
10
class TimerThread(Thread):
    front_end = None

    def __init__(self, event):
        Thread.__init__(self)
        self.stopped = event

    def run(self):
        while not self.stopped.wait(0.02):    
            FrontEnd.update_ui(self.front_end)

(TimerThreadfront_end元素在FrontEnd的init上设置)

问题出在update_bar_graph函数中。当setFixedSize调用被注释掉时,程序运行正常,但没有在我的应用程序中正确显示条形图的条形(QLabels)。 move函数似乎运行正常。但是,setFixedSize调用会导致此错误:

1
2
3
4
QObject::startTimer: Timers cannot be started from another thread
QObject::startTimer: Timers cannot be started from another thread
QObject::killTimer: Timers cannot be stopped from another thread
QObject::startTimer: Timers cannot be started from another thread

我完全不知道为什么会发生这种情况,为什么move功能看起来性质相似,效果很好。任何帮助将非常感激。
(如果我应该使用不同类型的计时器类或不同的方法在PyQt中绘制大矩形,我愿意接受任何这样的建议)。

编辑:

这是一些奇怪的东西。我第二天跑了两次,代码没有变化。 (我想......)有一次条形图没有显示但没有抛出任何错误。另一次我得到了这个:

1
2
3
4
5
6
7
8
9
7fdfaf931000-7fdfaf932000 r--p 0007a000 08:07 655633                     /usr/lib/x86_64-linux-gnu/libQt5DBus.so.5.5.1
7fdfaf932000-7fdfaf933000 rw-p 0007b000 08:07 655633                     /usr/lib/x86_64-linux-gnu/libQt5DBus.so.5.5.1
7fdfaf933000-7fdfaf934000 rw-p 00000000 00:00 0
7fdfaf934000-7fdfaf971000 r-xp 00000000 08:07 667112                     /usr/lib/x86_64-linux-gnu/libxkbcommon.so.0.0.0
7fdfaf971000-7fdfafb70000 ---p 0003d000 08:07 667112                     /usr/lib/x86_64-linux-gnu/libxkbcommon.so.0.0.0
7fdfafb70000-7fdfafb72000 r--p 0003c000 08:07 667112                     /usr/lib/x86_64-linux-gnu/libxkbcommon.so.0.0.0
7fdfafb72000-7fdfafb73000 rw-p 0003e000 08:07 667112                     /usr/lib/x86_64-linux-gnu/libxkbcommon.so.0.0.0
7fdfafb73000-7fdfafb7a000 r-xp 00000000 08:07 667110                     /usr/lib/x86_64-linux-gnu/libxkbcommon-x11.so.0.0.0
7fdfafb7a000-7fdfafd79000 ---p 00007000 08:07 667110                     /usr/lib/x86_64-linux-gnu/libxkbcommon-x11.so.0.0.0

我想我可能在PyQt5中发现了一个错误。


正如@mata所提到的,您的代码不是线程安全的。这可能是错误行为的来源,并且应该在进一步调试之前确定(这是相关的)。

它是线程不安全的原因是因为您直接从辅助线程与GUI对象进行交互。您应该从线程向主线程中的插槽发出信号,您可以安全地更新GUI。但是,这需要您使用QThread,无论如何,根据此帖子。

这需要进行以下更改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class TimerThread(QThread):
    update = pyqtSignal()

    def __init__(self, event):
        QThread.__init__(self)
        self.stopped = event

    def run(self):
        while not self.stopped.wait(0.02):    
            self.update.emit()

class FrontEnd(QWidget):
    def __init__(self):
        super().__init__()

        ... # code as in your original

        stop_flag = Event()    
        self.timer_thread = TimerThread(stop_flag)
        self.timer_thread.update.connect(self.update_ui)
        self.timer_thread.start()

我还修改了你的代码,以便它存储对FrontEnd对象中TimerThread的引用,这样就不会对线程进行垃圾回收。

我还要补充一点,这是一种每0.02秒触发更新的过于复杂的方式。您可以使用QTimer来调用update_ui方法并完全抛弃线程,但我采用的方法是您可能希望稍后对您的线程执行更复杂的操作,因此已经演示了如何安全地执行此操作!