Завершение потоков QThread и инициализация диалогового окна после этого

Прошу помощи в реализации функции завершения потоков и инициализации диалогового окна после этого. После того как отработает цикл до команды "STOP"

class Thread(QThread):
    def run(self): 
        for i in date_command:
            if "SYSCFGEX" in i:
                i = i.strip('"')
                self.msleep(1000 * 2)  # 2 сек.
            Thread.message_handler(self, id_dev, msg=i)

необходимо заверишть работу потоков из класса

class MainWindow(QMainWindow, Ui_MainWindow):
    def run_th(self):
            self.thread_1.start()
            self.thread_2.start()

Пробовал делать вот такую конструкцию:

            if "STOP" in msg:
                self.updateSignal.emit(
                    id_dev, "STOP", self.name_thread)   # <--- После этого сигнала должен завершаться поток
                # name_th = self.name_thread
                main_window.textEdit_plate_1.append("Сканирование завершено")
                MainWindow.stop_th(self, msg="STOP", name_thread=self.name_thread)

в функцию stop_th я попадаю, пробовал завершать через self.thread_1.finished, через self.thread_1.quit(), не выходит ничего.

Получаю ошибки:

AttributeError: 'Thread' object has no attribute 'thread_1'. Did you mean: 'thread'?
AttributeError: 'Thread' object has no attribute 'thread_2'. Did you mean: 'thread'?

Объясню для чего мне это надо:

Это надо для того, чтобы иницализировать диалоговое окно, в котором пользователь будет проводить манипуляции, и для того, чтобы откатить кнопку в изначальное состояние (START), так как приложение должно работать без перезагрузки, т.е. отработал поток по клику кнопки, еще раз можно кликнуть, чтобы он работал, и так необходимое количество раз.

Файл main.py, а также dialog_adress.py прилагаю

main.py

import sys
import serial
import serial.tools.list_ports
from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5.Qt import *

from reo_main_window import Ui_MainWindow
from dialog_adress import Ui_form_dialog_adress

from scanclass import ScanningReo


class Thread(QThread):
    updateSignal = QtCore.pyqtSignal(object, str, str)

    def __init__(self, plate, name_thread):
        super().__init__()
        self.number_port = plate
        self.name_thread = name_thread
        print(f'number_port = {self.number_port}; {self.name_thread}')

    def run(self):
        scan = ScanningReo(
            "serial_port", "date_from_cmd",
            "date_command", "name_freq", "name_dev")
        opn_srl_prt = scan.open_serial_port(self.number_port)
        opn_srl_prt.close()
        opn_srl_prt = scan.open_serial_port(self.number_port)
        id_dev = scan.device_identification(opn_srl_prt)

        print(id_dev)
        if "120" in id_dev:
            self.updateSignal.emit(id_dev, '120', self.name_thread)
            date_command = [f'"AT^SYSCFGEX="03",1\r"',
                            f'"AT^SYSCFGEX="01",2\r"', "AT^NETSCAN=0\r",
                            f'"AT^SYSCFGEX="01",3\r"', "AT^NETSCAN=0\r",
                            f'"AT^SYSCFGEX="02",4\r"', "AT^NETSCAN=1\r",
                            f'"AT^SYSCFGEX="03",5\r"', "AT^NETSCAN=3\r",
                            "STOP"
                            ]
        elif "821" in id_dev:
            if "821" in id_dev:
            self.updateSignal.emit(id_dev, '821', self.name_thread)
            date_command = [f'"AT^SYSCFGEX="03",1\r"',
                            f'"AT^SYSCFGEX="01",2\r"', "AT^NETSCAN=0\r",
                            f'"AT^SYSCFGEX="01",3\r"', "AT^NETSCAN=0\r",
                            f'"AT^SYSCFGEX="02",4\r"', "AT^NETSCAN=1\r",
                            f'"AT^SYSCFGEX="03",5\r"', "AT^NETSCAN=3\r",
                            "STOP"
                            ]

        for i in date_command:
            Thread.message_handler(self, id_dev, msg=i)
            if "SYSCFGEX" in i:
                i = i.strip('"')
                self.msleep(1000 * 2)  # 2 сек.
            while scan.send_comm(opn_srl_prt, i) == "STOP":
                self.msleep(1000 * 2)  # 2 сек.

    def message_handler(self, id_dev, msg):
        """ Функция "message_handler" обрабатывает поступающие сообщения
            и выводит их в ui.textEdit
        """
        if "120" in id_dev:
            if "NETSCAN" in msg:
                if "AT^NETSCAN=200" in msg:
                    self.updateSignal.emit(
                        id_dev, "AT^NETSCAN=200", self.name_thread)
                    main_window.textEdit_plate_2.append("Начинаю сканирование 2!")
                if "AT^NETSCAN=20,-110,1" in msg:
                    self.updateSignal.emit(
                        id_dev, "AT^NETSCAN=201", self.name_thread)
                    main_window.textEdit_plate_2.append("Начинаю сканирование 3!")
                if "AT^NETSCAN=20,-110,3" in msg:
                    self.updateSignal.emit(
                        id_dev, "AT^NETSCAN=203", self.name_thread)
                    main_window.textEdit_plate_2.append("Начинаю сканирование 4!")
            if "STOP" in msg:
                self.updateSignal.emit(
                    id_dev, "STOP", self.name_thread)   # <--- После этого сигнала должен завершаться поток
                # name_th = self.name_thread
                main_window.textEdit_plate_2.append("Сканирование завершено")
                MainWindow.stop_th(self, msg="STOP", name_thread=self.name_thread)  #!!!!!

        if "821" in id_dev:
            if "NETSCAN" in msg:
                if "AT^NETSCAN=200" in msg:
                    self.updateSignal.emit(
                        id_dev, "AT^NETSCAN=200", self.name_thread)
                    main_window.textEdit_plate_1.append("Начинаю сканирование 2!")
                if "AT^NETSCAN=201" in msg:
                    self.updateSignal.emit(
                        id_dev, "AT^NETSCAN=201", self.name_thread)
                    main_window.textEdit_plate_1.append("Начинаю сканирование 3!")
                if "AT^NETSCAN=203" in msg:
                    self.updateSignal.emit(
                        id_dev, "AT^NETSCAN=203", self.name_thread)
                    main_window.textEdit_plate_1.append("Начинаю сканирование 4!")
            if "STOP" in msg:
                self.updateSignal.emit(
                    id_dev, "STOP", self.name_thread)   # <--- После этого сигнала должен завершаться поток
                # name_th = self.name_thread
                main_window.textEdit_plate_1.append("Сканирование завершено")
                MainWindow.stop_th(self, msg="STOP", name_thread=self.name_thread)

class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.thread_1 = None
        self.thread_2 = None

        self.pushButton_start.clicked.connect(self.run_th)

    def run_th(self):
        """ Функция "run_th" запускает потоки с экземплярами класса """

        device_port = self.scan_ttyusb()
        plate1 = device_port[0]
        plate2 = device_port[1]
        icon = QtGui.QIcon()

        if self.thread_1 is None and self.thread_2 is None:
            self.thread_1 = Thread(plate1, 'Plate 1')
            self.thread_1.updateSignal.connect(self.update_thread)
            self.thread_1.start()

            self.thread_2 = Thread(plate2, 'Plate 2')
            self.thread_2.updateSignal.connect(self.update_thread)
            self.thread_2.start()

            self.pushButton_start.setText("Stop")
            icon.addPixmap(QtGui.QPixmap(":/icons_main/cancel-64.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
            self.pushButton_start.setIcon(icon)
            self.pushButton_start.setStyleSheet("QPushButton {\n"
                                               "    background-color: rgb(255, 255, 255);\n"
                                               "}")

        else:
            reply = QMessageBox.question(
                self,
                'ВНИМАНИЕ !',
                "Вы уверены, что хотите прервать выполнение программы?",
                QMessageBox.Yes, QMessageBox.No
            )
            if reply == QMessageBox.Yes:
                self.thread_1.terminate()
                self.thread_1 = None
                self.thread_2.terminate()
                self.thread_2 = None

                self.pushButton_start.setText("Start")
                icon.addPixmap(QtGui.QPixmap(":/icons_main/go-64.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
                self.pushButton_start.setIcon(icon)
                self.pushButton_start.setStyleSheet("QPushButton {\n"
                                                    "    color: rgb(255, 255, 255);\n"
                                                    "    background-color: rgb(17, 154, 40);\n"
                                                    "}")
-----------------------------------------------------------------
    def stop_th(self, msg, name_thread):
        if name_thread == 'Plate 1' and msg == 'STOP':
            self.thread_1.finished
        if name_thread == 'Plate 2' and msg == 'STOP':
            self.thread_1.finished

        if self.thread_1.quit() and self.thread_2.quit():
            self.pushButton_start.setText("Start")
            QtGui.QIcon().addPixmap(QtGui.QPixmap(":/icons_main/go-64.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
            self.pushButton_start.setIcon(QtGui.QIcon())
            self.pushButton_start.setStyleSheet("QPushButton {\n"
                                                "    color: rgb(255, 255, 255);\n"
                                                "    background-color: rgb(17, 154, 40);\n"
                                                "}")
-----------------------------------------------------------------
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
Эта часть кода не работает, так как потоки не прекращают свою работу.


    def update_thread(self, id_dev, plate, name_thread):
        if name_thread == 'Plate 1':
            if '821' in plate:
                self.label_plate_1.setText(f"{id_dev}")
            else:
                self.label_plate_1.setText(f"{id_dev}")
        if name_thread == 'Plate 2':
            if '120' in plate:
                self.label_plate_2.setText(f"{id_dev}")
            else:
                self.label_plate_2.setText(f"{id_dev}")

    def scan_ttyusb(self):
        """ Функция "scan_ttyusb" сканирует порты и возвращает номера портов
            к которым подключены нужные устройства
        """

        ports = list(serial.tools.list_ports.comports())
        result = ""
        for text in ports:
            if 'Pcui' in text[1]:
                txt = text[0]
                result = result + txt + ','
                device_port = result

        return device_port.split(",")

    def closeEvent(self, event):
        reply = QMessageBox.question(
            self,
            'Информация',
            "Вы уверены, что хотите закрыть приложение?",
            QMessageBox.Yes, QMessageBox.No
        )
        if reply == QMessageBox.Yes:
            if self.thread_1:
                self.thread_1.quit()
            del self.thread_1
            if self.thread_2:
                self.thread_2.quit()
            del self.thread_2

            super(MainWindow, self).closeEvent(event)
        else:
            event.ignore()

class DialogWindow(QDialog, Ui_form_dialog_adress):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        
        self.pushButton_save.clicked.connect(self.save_log)
        self.pushButton_cancel.clicked.connect(self.cancel_save)

    def create_window(self):    # Должно срабатывать после команды "СТОП" для обоих потоков!!!!!!!
        dialog_window.setFixedSize(440, 161)
        dialog_window.show()
    
    def save_log(self):
        pass
    
    def cancel_save(self):
        pass

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    main_window = MainWindow()
    main_window.setFixedSize(680, 572)
    main_window.show()
    
    dialog_window = DialogWindow()
    
    sys.exit(app.exec_())

dialog_adress.py


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_form_dialog_adress(object):
    def setupUi(self, form_dialog_adress):
        form_dialog_adress.setObjectName("form_dialog_adress")
        form_dialog_adress.resize(440, 161)
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap(":/icons_main/electrical_sensor_main-100.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        form_dialog_adress.setWindowIcon(icon)
        form_dialog_adress.setLayoutDirection(QtCore.Qt.RightToLeft)
        form_dialog_adress.setStyleSheet("QWidget{\n"
"    background-color: white;\n"
"}")
        self.pushButton_save = QtWidgets.QPushButton(form_dialog_adress)
        self.pushButton_save.setGeometry(QtCore.QRect(60, 100, 120, 41))
        icon1 = QtGui.QIcon()
        icon1.addPixmap(QtGui.QPixmap(":/icons_main/save-64.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.pushButton_save.setIcon(icon1)
        self.pushButton_save.setIconSize(QtCore.QSize(40, 40))
        self.pushButton_save.setObjectName("pushButton_save")
        self.pushButton_cancel = QtWidgets.QPushButton(form_dialog_adress)
        self.pushButton_cancel.setGeometry(QtCore.QRect(260, 100, 120, 41))
        icon2 = QtGui.QIcon()
        icon2.addPixmap(QtGui.QPixmap(":/icons_main/cancel-64.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.pushButton_cancel.setIcon(icon2)
        self.pushButton_cancel.setIconSize(QtCore.QSize(40, 40))
        self.pushButton_cancel.setObjectName("pushButton_cancel")
        self.label_adress = QtWidgets.QLabel(form_dialog_adress)
        self.label_adress.setGeometry(QtCore.QRect(10, 10, 420, 30))
        self.label_adress.setBaseSize(QtCore.QSize(0, 30))
        font = QtGui.QFont()
        font.setPointSize(14)
        self.label_adress.setFont(font)
        self.label_adress.setLayoutDirection(QtCore.Qt.RightToLeft)
        self.label_adress.setAlignment(QtCore.Qt.AlignCenter)
        self.label_adress.setObjectName("label_adress")
        self.lineEdit_adress = QtWidgets.QLineEdit(form_dialog_adress)
        self.lineEdit_adress.setGeometry(QtCore.QRect(10, 50, 420, 30))
        font = QtGui.QFont()
        font.setPointSize(14)
        self.lineEdit_adress.setFont(font)
        self.lineEdit_adress.setText("")
        self.lineEdit_adress.setObjectName("lineEdit_adress")

        self.retranslateUi(form_dialog_adress)
        QtCore.QMetaObject.connectSlotsByName(form_dialog_adress)

    def retranslateUi(self, form_dialog_adress):
        _translate = QtCore.QCoreApplication.translate
        form_dialog_adress.setWindowTitle(_translate("form_dialog_adress", "Aдрес местонахождения"))
        self.pushButton_save.setText(_translate("form_dialog_adress", "Сохранить"))
        self.pushButton_cancel.setText(_translate("form_dialog_adress", "Отмена"))
        self.label_adress.setText(_translate("form_dialog_adress", "Введите адрес местонахождения:"))


Ответы (1 шт):

Автор решения: S. Nick
  1. Нельзя взаимодействовать с виджетами в дополнительном потоке.
    Это не потокобезопасно. Надо использовать сигналы и слоты.
    Вам срочно надо изучить Signals & Slots и Support for Signals and Slots

Такие записи как:

        main_window.textEdit_plate_2.append("Начинаю сканирование 2!")

в дополнительном потоке делать нельзя.


Мы же с вами это уже проходили:

  • в дополнительном потоке class Thread(QThread):

    • создаем сигнал:

          updateSignal = QtCore.pyqtSignal(object, str, str)
      
    • испускаем сигнал:

          self.updateSignal.emit(id_dev, "AT^NETSCAN=203", self.name_thread)
      

      Сигналы испускаются при использовании метода emit(*args) связанного сигнала. args - необязательная последовательность аргументов для передачи в любые связанные слоты.

  • в основном потоке:

    • делаем привязку сигналов и слотов

          self.thread_1.updateSignal.connect(self.update_thread)
      
    • пишем слот, в который принимаем данные из дополнительного потока:

          def update_thread(self, id_dev, plate, name_thread):
              ...
      
              # и делаем то что нам надо, например, обновляем label 
              self.label_plate_1.setText(f"{id_dev}")
      
              # или добавляем в textEdit
              self.textEdit_plate_1.append("Начинаю сканирование 2!")
      
  • пока вы это не освоите - двигаться дальше нельзя.



  1. По вашему текущему вопросу все то же самое:
  • в дополнительном потоке class Thread(QThread):

    • создаем сигнал:

          class Thread(QThread):
              updateSignal = QtCore.pyqtSignal(object, str, str)
              finished = QtCore.pyqtSignal(str)                     # <----
              ...
      
    • испускаем сигнал: где-то где вы обнаруживаете, что поймали "STOP", предположительно:

          if "STOP" in msg:
              self.finished.emit(self.name_thread)                # <----  
      
  • в основном потоке:

    • иницализировать диалоговое окно:

          class MainWindow(QMainWindow, Ui_MainWindow):
              def __init__(self):
                  super().__init__()
                  self.setupUi(self)
                  self.thread_1 = None
                  self.thread_2 = None
                  self.pushButton_start.clicked.connect(self.run_th)
      
                  # +++ иницализировать диалоговое окно
                  self.dialogWindow = DialogWindow()              # <----
      
    • делаем привязку сигналов и слотов:

          self.thread_1 = Thread('plate1', 'Plate 1')
          self.thread_1.updateSignal.connect(self.update_thread)
          self.thread_1.finished.connect(self.finished_thread)       # <----
      
    • пишем слот, в который принимаем данные из дополнительного потока и делаем то что надо:

          def finished_thread(self, name_thread):
              print(f'name_thread = {name_thread}') 
              if name_thread == 'Plate 1':
                  self.thread_1.terminate() 
                  self.thread_1 = None        
              elif name_thread == 'Plate 2':
                  self.thread_2.terminate() 
                  self.thread_2 = None             
      
              if self.thread_1 is None and self.thread_2 is None:
                  print('## оба потока закрыты') 
      
                  # востанавливайте кнопки START
                  self.pushButton_start.setText("Start Thread`s")
      
                  # показывайте диалоговое окно
                  self.dialogWindow.create_window()
      
                  # и делайте другие нужные вещи для вас
      
  • и наконец :

          if __name__ == '__main__':
              app = QtWidgets.QApplication(sys.argv)
              main_window = MainWindow()
              main_window.setFixedSize(680, 572)
              main_window.show()
    
          #    dialog_window = DialogWindow()                   # убираем              
    
              sys.exit(app.exec_())
    
→ Ссылка