PyQt5“定时器不能从另一个线程启动"更改 QLabel 的大小时出错

PyQt5 quot;Timers cannot be started from another threadquot; error when changing size of QLabel(PyQt5“定时器不能从另一个线程启动更改 QLabel 的大小时出错)

本文介绍了PyQt5“定时器不能从另一个线程启动"更改 QLabel 的大小时出错的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在 Python 3.5 中遇到了一个奇怪的 PyQt5 问题.我有两个类,FrontEnd(QWidget)TimerThread(Thread).我在FrontEnd的init函数中定义了很多QLabel,都可以正常工作.

I am having a strange issue with PyQt5 in Python 3.5. I have two classes, FrontEnd(QWidget) and TimerThread(Thread). I have a number of QLabels defined in the init function of FrontEnd, all of which work properly.

显示的是FrontEnd的相关几个函数:

Shown are the relevant few functions of FrontEnd:

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 类:

And the entire TimerThread class:

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 上设置)

(The front_end element of TimerThread is set on init of FrontEnd)

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

The problem is in the update_bar_graph function. When the setFixedSize call is commented out, the program runs fine, although without properly displaying the bars of the bar graph in my application (which are QLabels). The move function seems to run properly. However, the setFixedSize call causes this error:

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 中绘制大矩形,我愿意接受任何此类建议).

I have absolutely no idea why this occurs, and why the move function, seemingly similar in nature, works fine. Any help would be much appreciated. (If I should be using a different sort of timer class or different methods for drawing large rectangles in PyQt, I am open to any such suggestions).

这是一些奇怪的东西.第二天我运行了两次,没有更改代码.(我认为...)有一次条形图没有显示,但没有引发错误.另一次我得到了这个:

This is some weird stuff. I ran it twice the next day, with no changes to the code. (I think...) One time the bar graphs did not display but no errors were thrown. The other time I got this:

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 中发现了一个错误.

I think I may have found a bug in PyQt5.

推荐答案

正如@mata 所述,您的代码不是线程安全.这可能是错误行为的来源,当然应该在进一步调试之前修复(this 是相关的).

As mentioned by @mata your code is not thread safe. This is likely where the errant behaviour is coming from, and should certainly be fixed before debugging further (this is related).

它是线程不安全的原因是因为您直接从辅助线程与 GUI 对象交互.相反,您应该从线程向主线程中的插槽发出信号,您可以在其中安全地更新 GUI.但是,这需要您使用 QThread,无论如何根据这篇文章推荐.

The reason it is thread unsafe is because you are interacting with GUI objects from the secondary thread directly. You should instead emit a signal from your thread to a slot in the main thread where you can safely update your GUI. This however requires you use a QThread which is recommended anyway as per this post.

这需要进行以下更改:

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

我还修改了您的代码,以便它将对 TimerThread 的引用存储在 FrontEnd 对象中,这样线程就不会被垃圾回收.

I've also modified your code so that it stores a reference to the TimerThread in the FrontEnd object so the thread is not garbage collected.

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

I would also add that this is an overly complex way of triggering an update every 0.02 seconds. You could use a QTimer to just call the update_ui method and ditch threads entirely but I'm taking the approach that you will likely want to do something more complex with your threads later, so have demonstrated how to do it safely!

这篇关于PyQt5“定时器不能从另一个线程启动"更改 QLabel 的大小时出错的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!

本文标题为:PyQt5“定时器不能从另一个线程启动"更改 QLabel 的大小时出错

基础教程推荐