Завершение потоков 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 шт):
- Нельзя взаимодействовать с виджетами в дополнительном потоке.
Это не потокобезопасно. Надо использовать сигналы и слоты.
Вам срочно надо изучить 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!")
пока вы это не освоите - двигаться дальше нельзя.
- По вашему текущему вопросу все то же самое:
в дополнительном потоке
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_())