PyQt Threading: Програмата за взаимодействие с GUI се срива

Проектирам програма за редактиране на DICOM. По-конкретно, имам проблеми с правилното взаимодействие с моя PyQt UI.

Искам да мога да щракна върху бутон „пауза“ и „стоп“, за да спра на пауза или да спра функцията си за редактиране. Моята функция за редактиране отнема значително време за обработка/преминаване. В зависимост от броя на файловете, които редактира, може да отнеме от 30 секунди до повече от час. Поради това реших да хвърля моята функция за редактиране в собствена нишка, използвайки собствените възможности за нишки на Qt. Успях да накарам нишката да работи, т.е.: от моя клас MainWindow мога да щракна върху бутон, който инициализира моя клас за редактиране (class edit(QThread), но взаимодействието с GUI все още срива програмата и не съм сигурен защо! По-долу I добавих проба от общата структура на кода/настройката, която използвам.

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 stopped(self) би било достатъчно за промяна на стойността на stop_flag на 1. Случайно пропуснах ред от код, когато копирах и поставих: self.stop_flag = 0 в init на MainWindow. Промених го в горния въпрос   -  person JoshG    schedule 07.06.2017
comment
@eyllanesc Аз съм нов в pyqt/qt като цяло. Мислех, че нишката на класа ще се наследи от MainWindow?   -  person JoshG    schedule 07.06.2017
comment
Не, вие създавате нова нишка, основната нишка е GUI и могат да се създават само вторични нишки.   -  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 от моя клас mainwindow към класа 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, можете да извикате метода за затваряне, който затваря прозореца.

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

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