Как настроить QThread для обработки массива и его передачи в другую функцию основной программы?
Пытаюсь сделать небольшую программу и заодно освоить PyQt6.
Программа должна забирать некие данные из файла Excel, отправлять их в качестве запроса на сайт в Интернете и возвращать некий новый набор данных, которые записываются в другой файл Excel.
Естественно, на этапе отправки и ожидания ответа от сайта интерфейс зависает.
Пробую решить эту проблему с использованием QThread, но возникает ошибка:
Process finished with exit code -1073740791 (0xC0000409)
Судя по всему, ошибка возникает при инициализации потока, но в чем проблема - понять никак не могу.
Пример работающего кода, но без QThread и с зависанием GUI:
import sys
import test_ui
from PyQt6 import QtWidgets
import time
def select_loadfile():
file = QtWidgets.QFileDialog.getOpenFileName(parent=window, filter="*xlsx")
if file[0] == '':
return None
else:
window.line1.setText(file[0])
def select_savefile():
file = QtWidgets.QFileDialog.getOpenFileName(parent=window, filter="*xlsx")
if file[0] == '':
return None
else:
window.line2.setText(file[0])
def before_working():
start_array = ['Data1', 'Data2', 'Data3'] # Имитация полученных из файла Excel данных
window.progr_bar.setRange(0, len(start_array))
return start_array
def working(input_array):
print(f'Пришло в working {input_array}')
result_array = []
for item in input_array:
time.sleep(3) # Имитация ожидания ответа от сайта
result_array.append(item)
pb_value = window.progr_bar.value()
window.progr_bar.setValue(pb_value + 1)
return result_array
def after_working(input_array): # Имитация сохранения данных в файле
window.button1.setDisabled(False)
window.button2.setDisabled(False)
window.button_start.setDisabled(False)
if window.line2.displayText() != '':
print(f'Пришло в get_result {input_array} без запроса сохранения')
window.inf_msg('Успех', 'Процесс успешно завершен')
else:
select_savefile()
if window.line2.displayText() != '':
print(f'Пришло в get_result {input_array} с запросом сохранения')
window.inf_msg('Успех', 'Процесс успешно завершен')
else:
window.err_msg('Результаты проверки не сохранены')
window.progr_bar.setValue(0)
def main():
if window.line1.displayText() != '':
after_working(working(before_working()))
else:
window.err_msg('Не выбран файл загрузки')
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = test_ui.MyWin()
window.button1.clicked.connect(select_loadfile)
window.button2.clicked.connect(select_savefile)
window.button_start.clicked.connect(main)
window.show()
sys.exit(app.exec())
Файл графического интерфейса test_ui.py, в нем же класс Worker для отправки в поток:
from PyQt6 import QtWidgets, QtCore
import time
class MyWin(QtWidgets.QWidget):
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
self.resize(300, 200)
icon = self.style().standardIcon(QtWidgets.QStyle.StandardPixmap.SP_MessageBoxInformation)
self.setWindowIcon(icon)
self.setWindowTitle("Выполнение долгой задачи")
self.line1 = QtWidgets.QLineEdit()
self.line1.setReadOnly(True)
self.line2 = QtWidgets.QLineEdit()
self.line2.setReadOnly(True)
self.button1 = QtWidgets.QPushButton("Выбрать файл")
self.button2 = QtWidgets.QPushButton("Выбрать файл")
self.button_start = QtWidgets.QPushButton("НАЧАТЬ")
self.progr_bar = QtWidgets.QProgressBar()
self.progr_bar.setValue(0)
self.box1 = QtWidgets.QHBoxLayout()
self.box1.addWidget(self.line1)
self.box1.addWidget(self.button1)
self.box2 = QtWidgets.QHBoxLayout()
self.box2.addWidget(self.line2)
self.box2.addWidget(self.button2)
self.main_box = QtWidgets.QVBoxLayout()
self.main_box.addLayout(self.box1)
self.main_box.addLayout(self.box2)
self.main_box.addWidget(self.button_start)
self.main_box.addWidget(self.progr_bar)
self.setLayout(self.main_box)
# Функция вывода сообщения об ошибке с заданным текстом
def err_msg(self, msg_text):
dialog = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Icon.Warning, 'Ошибка', msg_text,
buttons=QtWidgets.QMessageBox.StandardButton.Ok, parent=self)
icon = self.style().standardIcon(QtWidgets.QStyle.StandardPixmap.SP_MessageBoxWarning)
dialog.setWindowIcon(icon)
dialog.exec()
# Функция вывода информационного сообщения с заданным текстом
def inf_msg(self, msg_header, msg_text):
dialog = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Icon.Information, msg_header, msg_text,
buttons=QtWidgets.QMessageBox.StandardButton.Ok, parent=self)
icon = self.style().standardIcon(QtWidgets.QStyle.StandardPixmap.SP_MessageBoxInformation)
dialog.setWindowIcon(icon)
dialog.exec()
class Worker(QtCore.QObject):
progress = QtCore.pyqtSignal()
finished = QtCore.pyqtSignal(object)
def __init__(self, input_array):
super().__init__()
self.input_array = input_array
def start(self):
print('Начата работа в отдельном потоке')
res_array = []
for item in self.input_array:
time.sleep(3) # Имитация ожидания ответа от сайта
res_array.append(item)
self.progress.emit()
print(f'Массив в потоке {res_array}')
self.finished.emit(res_array)
Файл с основной функцией и попыткой использовать QThread - не работает:
import sys
import test_ui
from PyQt6 import QtWidgets, QtCore
def select_loadfile():
file = QtWidgets.QFileDialog.getOpenFileName(parent=window, filter="*xlsx")
if file[0] == '':
return None
else:
window.line1.setText(file[0])
def select_savefile():
file = QtWidgets.QFileDialog.getOpenFileName(parent=window, filter="*xlsx")
if file[0] == '':
return None
else:
window.line2.setText(file[0])
def before_working():
start_array = ['Data1', 'Data2', 'Data3'] # Имитация данных из файла Excel
window.progr_bar.setRange(0, len(start_array))
return start_array
def working(input_array):
print(f'Пришло в working {input_array}')
result_array = []
thread_arr = input_array
def update_progressbar():
pb_value = window.progr_bar.value()
window.progr_bar.setValue(pb_value + 1)
def get_result(inp_data):
print(f'пришло в get_result {inp_data}')
for item in inp_data:
result_array.append(item)
window.button1.setDisabled(True)
window.button2.setDisabled(True)
window.button_start.setDisabled(True)
obj = test_ui.Worker(thread_arr) # Попытка создать объект и направить в поток
t = QtCore.QThread()
obj.moveToThread(t)
t.started.connect(obj.start)
obj.finished.connect(t.quit)
obj.finished.connect(get_result)
obj.progress.connect(update_progressbar)
t.start()
print('Поток запущен')
return result_array
def after_working(input_array): # Имитация функции для записи результатов в файл Excel
window.button1.setDisabled(False)
window.button2.setDisabled(False)
window.button_start.setDisabled(False)
if window.line2.displayText() != '':
print(f'пришло в get_result {input_array}')
window.inf_msg('Успех', 'Процесс успешно завершен')
else:
select_savefile()
if window.line2.displayText() != '':
print(f'пришло в get_result {input_array}')
window.inf_msg('Успех', 'Процесс успешно завершен')
else:
window.err_msg('Результаты проверки не сохранены')
window.progr_bar.setValue(0)
def main():
if window.line1.displayText() != '':
after_working(working(before_working()))
else:
window.err_msg('Не выбран файл загрузки')
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = test_ui.MyWin()
window.button1.clicked.connect(select_loadfile)
window.button2.clicked.connect(select_savefile)
window.button_start.clicked.connect(main)
window.show()
sys.exit(app.exec())
Ответы (2 шт):
Чтобы получить реальную ошибку, надо запускать приложение в консоли/терминале/CMD.
Sorry, я не проверял какую реальную ошибку вы получаете.
Вам надо запомнить, что нельзя использовать в основном потоке while True: и time.sleep(3) - это блокирует интерфейс.
Вам надо запомнить, что нельзя взаимодействовать с виджетами в дополнительном потоке, это небезопасно.
Надо использовать сигналы и слоты.
Как вариант, это может выглядеть примерно так:
import sys
#import test_ui
#from PyQt6 import QtWidgets, QtCore
from PyQt5 import QtWidgets, QtCore
#import time
def before_working():
start_array = ['Data1', 'Data2', 'Data3'] # Имитация полученных из файла Excel данных
# window.progr_bar.setRange(0, len(start_array))
return start_array
# !!! +++ vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
class Thread(QtCore.QThread):
threadSignal = QtCore.pyqtSignal(int) # !!!
def __init__(self):
super().__init__()
self.value = 0
self.input_array = []
self.result_array = []
def run(self):
print(f'run(self): {self.input_array}') #
self.result_array = []
for item in self.input_array:
self.result_array.append(item)
self.msleep(3000) # Имитация ожидания ответа от сайта
self.value += 1
self.threadSignal.emit(self.value) # !!!
# !!! +++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
class MyWin(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.resize(300, 200)
icon = self.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_MessageBoxInformation)
self.setWindowIcon(icon)
self.setWindowTitle("Выполнение долгой задачи")
self.line1 = QtWidgets.QLineEdit()
self.line1.setReadOnly(True)
self.line2 = QtWidgets.QLineEdit()
# ? self.line2.setReadOnly(True)
self.button1 = QtWidgets.QPushButton("Выбрать файл")
self.button1.clicked.connect(self.select_loadfile) # +
self.button2 = QtWidgets.QPushButton("Выбрать файл")
self.button2.clicked.connect(self.select_savefile) # +
self.button_start = QtWidgets.QPushButton("НАЧАТЬ")
self.button_start.clicked.connect(self.begin) # +
self.progr_bar = QtWidgets.QProgressBar()
self.progr_bar.setValue(0)
self.box1 = QtWidgets.QHBoxLayout()
self.box1.addWidget(self.line1)
self.box1.addWidget(self.button1)
self.box2 = QtWidgets.QHBoxLayout()
self.box2.addWidget(self.line2)
self.box2.addWidget(self.button2)
self.main_box = QtWidgets.QVBoxLayout(self)
self.main_box.addLayout(self.box1)
self.main_box.addLayout(self.box2)
self.main_box.addWidget(self.button_start)
self.main_box.addWidget(self.progr_bar)
# !!! +++ vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
self.thread = Thread() # !!!
self.thread.threadSignal.connect(self.on_threadSignal) # !!!
self.thread.finished.connect( # !!!
lambda: self.after_working(self.thread.result_array))
def on_threadSignal(self, value): # !!!
print(f'on_threadSignal(self, value): {value}') #
self.progr_bar.setValue(value)
# Функция вывода сообщения об ошибке с заданным текстом
def err_msg(self, msg_text):
dialog = QtWidgets.QMessageBox(
QtWidgets.QMessageBox.Icon.Warning,
'Ошибка', msg_text,
buttons=QtWidgets.QMessageBox.StandardButton.Ok, parent=self)
icon = self.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_MessageBoxWarning)
dialog.setWindowIcon(icon)
dialog.exec()
# Функция вывода информационного сообщения с заданным текстом
def inf_msg(self, msg_header, msg_text):
dialog = QtWidgets.QMessageBox(
QtWidgets.QMessageBox.Icon.Information,
msg_header, msg_text,
buttons=QtWidgets.QMessageBox.StandardButton.Ok, parent=self)
icon = self.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_MessageBoxInformation)
dialog.setWindowIcon(icon)
dialog.exec()
def select_loadfile(self):
filePath, ok = QtWidgets.QFileDialog.getOpenFileName(
self, filter="*xlsx")
if filePath:
self.line1.setText(filePath)
def select_savefile(self):
filePath, ok = QtWidgets.QFileDialog.getOpenFileName(
self, filter="*xlsx")
if filePath:
self.line2.setText(filePath)
def begin(self):
print(f'line1.displayText() = {window.line1.displayText()}') #
if self.line1.displayText():
startArray = before_working()
if not startArray:
self.err_msg('Пустой startArray ???')
return
self.progr_bar.setRange(0, len(startArray))
self.thread.input_array = startArray
self.thread.value = 0
self.button1.setDisabled(True)
self.button2.setDisabled(True)
self.button_start.setDisabled(True)
self.thread.start() # !!!
# self.after_working(working(startArray))
else:
self.err_msg('Не выбран файл загрузки.')
def after_working(self, input_array): # Имитация сохранения данных в файле
print(f'\ndef after_working(input_array):{input_array}\n') #
self.button1.setDisabled(False)
self.button2.setDisabled(False)
self.button_start.setDisabled(False)
self.progr_bar.setValue(0)
# ? vvv
if self.line2.displayText() != '':
print(f'Пришло в get_result {input_array} без запроса сохранения')
self.inf_msg('Успех', 'Процесс успешно завершен')
else:
self.select_savefile()
if self.line2.displayText() != '':
print(f'Пришло в get_result {input_array} с запросом сохранения')
self.inf_msg('Успех', 'Процесс успешно завершен')
else:
self.err_msg('Результаты проверки не сохранены')
# !!! +++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
# window = test_ui.MyWin()
window = MyWin() # +++
window.show()
sys.exit(app.exec())
Установите свои импорты и проверьте.
Спасибо большое S. Nick, все заработало!
Вам надо запомнить, что нельзя использовать в основном потоке while True: и time.sleep(3) - это блокирует интерфейс
Да, я использовал time.sleep(3), чтобы в примере сделать имитацию ожидания ответа от сайта и соответственно зависание интерфейса.
Единственное, что в функции def after_working перенес сброс статусбара в самый конец, уже после сохранения/несохранения результата.
def after_working(self, input_array): # Имитация сохранения данных в файле
print(f'\ndef after_working(input_array):{input_array}\n') #
self.button1.setDisabled(False)
self.button2.setDisabled(False)
self.button_start.setDisabled(False)
if self.line2.displayText() != '':
print(f'Пришло в get_result {input_array} без запроса сохранения')
self.inf_msg('Успех', 'Процесс успешно завершен')
else:
self.select_savefile()
if self.line2.displayText() != '':
print(f'Пришло в get_result {input_array} с запросом сохранения')
self.inf_msg('Успех', 'Процесс успешно завершен')
else:
self.err_msg('Результаты проверки не сохранены')
self.progr_bar.setValue(0) # Сброс статусбара
