PyQt Threading: взаимодействие с графическим интерфейсом вызывает сбой программы

Я разрабатываю программу для редактирования DICOM. В частности, у меня возникают проблемы с надлежащим взаимодействием с моим пользовательским интерфейсом PyQt.

Я хочу иметь возможность нажимать кнопку «пауза» и кнопку «стоп», чтобы либо приостановить, либо остановить мою функцию редактирования. Моя функция редактирования требует значительного количества времени для обработки/зацикливания. В зависимости от количества редактируемых файлов это может занять от 30 секунд до более часа. Из-за этого я решил вынести свою функцию редактирования в отдельный поток, используя собственные возможности многопоточности Qt. Мне удалось заставить поток работать, то есть: из моего класса MainWindow я могу нажать кнопку, которая инициализирует мой класс редактирования (редактирование класса (QThread), однако взаимодействие с графическим интерфейсом по-прежнему приводит к сбою программы, и я не знаю, почему! Ниже я добавил образец общей структуры/настройки кода, которую я использую.

class anonymizeThread(QThread):
    def __init__(self):
        QThread.__init__(self)


    def __del__(self):
        self.wait()

    #def sendAnon(self, progress_val):
     #   self.completed = 0
      #  return self.completed


    def run(self):
            # while self.completed < 100:
            #     self.completed += 0.00001
            #     self.emit(QtCore.SIGNAL('PROGRESS'), self.completed)
            # ANONYMIZE FUNCTION!
            i = 0
            #flag = self.stop_flag
            while i < 10000000: # and self.stop_flag is not 1:
                print(i)
                i+=1
            print('i didnt enter the loop')


class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setupUi(self)

        # connect the buttons
        self.worker = anonymizeThread()
        self.anonbtn.clicked.connect(self.anonymize)
        self.open_directory.clicked.connect(self.open_dir)
        self.pause.clicked.connect(self.paused)
        self.stopbtn.clicked.connect(self.stopped)

        # block button signals to start
        self.pause.blockSignals(True)
        self.stopbtn.blockSignals(True)

        self.dir_name = None
        self.pause_flag = None
        self.stop_flag = None
        self.anon_flag = None

        # This is how we quit from the main menu "File" option
        extractAction = self.actionQuit_Ctrl_Q
        extractAction.setShortcut("Ctrl+Q")
        extractAction.setStatusTip('Leave The App')
        extractAction.triggered.connect(self.close_application)



    def updateProgressBar(self,val):
        self.progressBar.setValue(val)
    def close_application(self):
        choice = QMessageBox.question(self, 'Just had to check...', "Are you sure you want to exit?", QMessageBox.Yes | QMessageBox.No)
        if choice == QMessageBox.Yes:
            sys.exit()
        else:
            pass

    def anonymize(self):
        self.pause.blockSignals(False)
        self.stopbtn.blockSignals(False)
        self.worker.start()
        # check if directory chosen
        # self.progressBar.setMaximum(len(dcm)
        # start our anon thread!


    def paused(self):
        #only if running
        if self.pause_flag is 0:
            self.pause_flag = 1
            self.pause.setText('Start')
        elif self.pause_flag is 1:
            self.pause_flag = 0
            self.pause.setText('Pause')
        else:
            pass

    def stopped(self): # need a self.stop() for anonThread

        choice = QMessageBox.question(self,'Stop', "Are you sure you want to stop? You will not be able to pick up exactly where you left off.",
                              QMessageBox.Yes | QMessageBox.No)
        if choice == QMessageBox.Yes:
            self.stop_flag = 1
            #self.stopbtn.blockSignals(True)
            #self.paused.blockSignals(True)
        else:
            pass


    def open_dir(self):
        self.dir_name = str(QFileDialog.getExistingDirectory(self, "Select Directory"))
        if len(self.dir_name) is not 0:
            self.anon_flag = 0




def main():
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

person JoshG    schedule 07.06.2017    source источник
comment
Почему вы используете blockSignals? Также поделитесь воспроизводимым кодом, поскольку атрибут stop_flag не существует в классе editThread.   -  person eyllanesc    schedule 07.06.2017
comment
Наверняка должно быть: self.worker.stop_flag = 1? И, очевидно, вам нужно поместить self.stop_flag = 0 в editThread.__init__.   -  person ekhumoro    schedule 07.06.2017
comment
Где трассировка стека?   -  person MrEricSir    schedule 07.06.2017
comment
Я хотел заблокировать возможность нажатия кнопки остановки, а также кнопки паузы до запуска потока редактирования. Я думал, что вызова self.stop_flag = 1 в классе MainWindow и в функции def stop(self) будет достаточно для изменения значения stop_flag на 1. Я случайно пропустил строку кода, когда копировал и вставлял: self.stop_flag = 0 в инициализации MainWindow. Я изменил его в предыдущем вопросе   -  person JoshG    schedule 07.06.2017
comment
@eyllanesc Я новичок в pyqt/qt в целом. Я думал, что поток класса наследует себя от MainWindow?   -  person JoshG    schedule 07.06.2017
comment
Нет, вы создаете новый поток, основной поток — это графический интерфейс, а создавать можно только второстепенные потоки.   -  person eyllanesc    schedule 07.06.2017
comment
Какая кнопка запустит поток editThread?   -  person eyllanesc    schedule 07.06.2017
comment
Что такое кнопка запуска, остановки и паузы?   -  person eyllanesc    schedule 07.06.2017
comment
Я удалил их, чтобы попытаться упростить проблему (и сосредоточиться только на кнопке остановки). Извини за это. Я отредактировал исходный пост, чтобы включить все, но я не полностью интегрировал / не работал над функциональностью кнопки паузы. Редактирование (анонимизация в чистой копии моего кода) вызывает Qthread как self.worker.start() Как мне передать переменную, например: stop_flag, из моего класса основного окна в класс QThread в режиме реального времени?   -  person JoshG    schedule 07.06.2017
comment
Будет ли это подходящим временем для использования глобальной переменной?   -  person JoshG    schedule 08.06.2017
comment
попробуй с моим кодом   -  person eyllanesc    schedule 08.06.2017


Ответы (2)


Желательно не обращаться к флагам напрямую, лучше делать это через функции, чтобы использовать их прозрачно, для этого тот же класс должен проверять задачи.

Также хорошо дать небольшую задержку, чтобы приложение могло справиться с графической частью, еще одно возможное улучшение — избежать usat sys.exit, можно было вызвать метод close, закрывающий окно.

В следующем коде я реализовал методы остановки и паузы.

class anonymizeThread(QThread):
    def __init__(self):
        QThread.__init__(self)
        self.onRunning = True
        self.onStop = False


    def __del__(self):
        self.wait()

    def stop(self):
        self.onStop = True

    def pause(self):
        if self.isRunning():
            self.onRunning = not self.onRunning


    def run(self):
            i = 0
            #flag = self.stop_flag
            while i < 10000000:
                if self.onRunning: # and self.stop_flag is not 1:
                    print(i)
                    i+=1

                if self.onStop:
                    break
                QThread.msleep(10)
            print('i didnt enter the loop')


class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setupUi(self)


        # connect the buttons
        self.worker = anonymizeThread()
        self.anonbtn.clicked.connect(self.anonymize)
        self.pause.clicked.connect(self.paused)
        self.stopbtn.clicked.connect(self.stopped)

        # block button signals to start
        self.pause.blockSignals(True)
        self.stopbtn.blockSignals(True)

    def close_application(self):
        choice = QMessageBox.question(self, 'Just had to check...', "Are you sure you want to exit?", QMessageBox.Yes | QMessageBox.No)
        if choice == QMessageBox.Yes:
            self.close()

    def anonymize(self):
        self.pause.blockSignals(False)
        self.stopbtn.blockSignals(False)
        self.worker.start()


    def paused(self):
        self.worker.pause()

    def stopped(self): # need a self.stop() for anonThread

        choice = QMessageBox.question(self,'Stop', "Are you sure you want to stop? You will not be able to pick up exactly where you left off.",
                              QMessageBox.Yes | QMessageBox.No)
        if choice == QMessageBox.Yes:
            self.worker.stop()
person eyllanesc    schedule 07.06.2017
comment
Нужен ли QThread.msleep()? Технически это замедлит работу этого потока. Без этого был бы шанс, что нажатие кнопки все еще может привести к сбою программы? Спасибо за реализацию паузы, работает отлично. Я бы проголосовал, но, видимо, для этого мне нужно 15 репутации :) - person JoshG; 08.06.2017

Спасибо @eyllansec и @ekhumoro..

В приведенном выше коде все экземпляры self.stop_flag = ... должны были быть self.worker.stop_flag = ..., поскольку они изменяют переменную, которая должна использоваться в рабочем классе/потоке. Моя ошибка заключалась в том, что оба класса унаследовали одно и то же «я».

Если есть другие ошибки и / или лучшие объяснения того, что я сделал неправильно, пожалуйста, опубликуйте ответ, и я приму его!

person JoshG    schedule 07.06.2017