2017-02-21 11 views
0

У меня возникла странная проблема с PyQt5 в Python 3.5. У меня есть два класса, FrontEnd(QWidget) и TimerThread(Thread). У меня есть число QLabel s, определенное в функции init от FrontEnd, все из которых работают правильно.PyQt5 Ошибка «Таймеры не могут запускаться из другого потока» при изменении размера QLabel

Показаны соответствующие несколько функций 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:

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) 

(front_end элемент TimerThread устанавливается на инициализации из FrontEnd)

Проблема заключается в функции update_bar_graph. Когда вызов setFixedSize закомментирован, программа работает нормально, хотя без надлежащего отображения полос гистограммы в моем приложении (которые являются QLabels). Функция move работает нормально. Однако setFixedSize вызов вызывает эту ошибку:

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, я открыт для любых таких предложений).

EDIT:

Это некоторые странные вещи. Я запускал его два раза на следующий день, без изменений кода. (Я думаю ...) Однажды гистограммы не отображались, но ошибок не было. В другой раз я получил это:

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.

+0

Btw, дружелюбный совет, если вы планируете запустить программу PyQt в Windows, убегайте от 'QThread' насколько это возможно. Это приводит к случайным сбоям и приведет вас в бешенство. –

+1

Вы не должны вызывать методы, которые обновляют QObjects [из другого потока] (http://doc.qt.io/qt-5/thread-basics.html#simultaneous-access-to-data). Вы должны создать сигнал, подключить его к слоту, а затем вы можете безопасно испускать этот сигнал из другого потока, обновления будут обрабатываться в цикле событий объекта ['lives'] (http://doc.qt.io/ qt-5/thread-basics.html # qobject-and-threads). – mata

+0

@TheQuantumPhysicist - Нет, если это случается, вероятность того, что вы на самом деле не поняли, как использовать потоки в Qt, и что вы делаете что-то неправильно , Обвинение Qt для ошибок программирования никому не поможет. – mata

ответ

2

Как указано @mata, ваш код не является безопасным для потолка. Вероятно, это происходит из-за ошибочного поведения и, безусловно, должно быть исправлено до отладки (this).

Причина, по которой эта проблема небезопасна, заключается в том, что вы напрямую взаимодействуете с объектами GUI из вторичной нити. Вместо этого вы должны излучать сигнал из вашего потока в слот в основном потоке, где вы можете безопасно обновлять свой графический интерфейс. Это, однако, требует использования QThread, который рекомендуется в любом случае в соответствии с 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 объекта, поэтому поток мусора не собираются.

Я также хотел бы добавить, что это чрезмерно сложный способ запуска обновления каждые 0,02 секунды. Вы можете использовать QTimer, чтобы просто вызвать метод update_ui и полностью потопить потоки, но я придерживаюсь подхода, который, скорее всего, вы захотите сделать что-то более сложное с вашими потоками позже, поэтому продемонстрировали, как сделать это безопасно!

Смежные вопросы