PyQt5“无法从另一个线程启动计时器"更改QLabel大小时出现错误 [英] PyQt5 "Timers cannot be started from another thread" error when changing size of QLabel

查看:103
本文介绍了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的初始值上设置的)

(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 所述,您的代码不是线程安全.这很可能来自错误行为,因此应该在进一步调试之前解决(相关).

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大小时出现错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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