Зависает интерфейс при выполнение цикла во втором потоке в PyQt5
Запускаю функцию test_func() во втором потоке, в котором есть цикл, из основного класса App, и интерфейс виснет.
Если, к примеру, перенести цикл из test_func() в run(), то интерфейс не зависает.
Разница между этими функциями в том, что test_func() вызывается emit'ом из основного класса App, а run() самовоспроизводится при старте класса ThreadForFunc.
Надеюсь, моя проблема ясна.
Код:
Примечание: первые три класса можете пропустить, в них находятся окна. Сама проблема кроется в последних двух классах - ThreadForFunc и App
import sys
#import time
from PyQt5 import QtCore, QtWidgets, QtGui
from PyQt5.QtWidgets import QWidget, QGraphicsDropShadowEffect, QProgressBar
from PyQt5.QtCore import QThread, QTimer, Qt
class Ui_MainWindow(object): # основное окно
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(224, 240, 291, 111))
self.pushButton.setObjectName("pushButton")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(190, 70, 351, 91))
font = QtGui.QFont()
font.setPointSize(26)
self.label.setFont(font)
self.label.setAlignment(QtCore.Qt.AlignCenter)
self.label.setObjectName("label")
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton.setText(_translate("MainWindow", "PushButton"))
self.label.setText(_translate("MainWindow", "ПЕРВОЕ ОКНО"))
class Ui_Form(object): # окно прогресса
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(431, 262)
font = QtGui.QFont()
font.setPointSize(10)
Form.setFont(font)
Form.setStyleSheet("background-color: #21222D;")
self.pushButton = QtWidgets.QPushButton(Form)
self.pushButton.setGeometry(QtCore.QRect(150, 190, 131, 31))
font = QtGui.QFont()
font.setFamily("Golos Text")
font.setPointSize(9)
font.setBold(True)
font.setWeight(75)
self.pushButton.setFont(font)
self.pushButton.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
self.pushButton.setStyleSheet("background-color: white;\n"
"border: 2px solid white;\n"
"border-radius: 15px;\n"
"color: #21222D;\n"
"")
self.pushButton.setObjectName("pushButton")
self.progressBar = QtWidgets.QProgressBar(Form)
self.progressBar.setGeometry(QtCore.QRect(40, 150, 351, 2))
font = QtGui.QFont()
font.setFamily("Golos Text")
font.setPointSize(9)
font.setBold(True)
font.setWeight(75)
self.progressBar.setFont(font)
self.progressBar.setMouseTracking(False)
self.progressBar.setTabletTracking(False)
self.progressBar.setAcceptDrops(False)
self.progressBar.setLayoutDirection(QtCore.Qt.LeftToRight)
self.progressBar.setAutoFillBackground(False)
self.progressBar.setStyleSheet("QProgressBar{\n"
" background-color: #555661;\n"
" color: #16171E;\n"
" text-align: center;\n"
" max-height: 2.45px;\n"
" min-height: 2.45px;\n"
"}\n"
"\n"
"QProgressBar::chunk{\n"
" background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0 #FFC468, stop:1 #F27F7D);\n"
"}")
self.progressBar.setProperty("value", 24)
self.progressBar.setTextVisible(True)
self.progressBar.setOrientation(QtCore.Qt.Horizontal)
self.progressBar.setInvertedAppearance(False)
self.progressBar.setObjectName("progressBar")
self.label = QtWidgets.QLabel(Form)
self.label.setGeometry(QtCore.QRect(40, 50, 351, 31))
font = QtGui.QFont()
font.setFamily("Golos Text")
font.setPointSize(12)
font.setBold(True)
font.setWeight(75)
self.label.setFont(font)
self.label.setStyleSheet("color: white;")
self.label.setAlignment(QtCore.Qt.AlignCenter)
self.label.setObjectName("label")
self.label_3 = QtWidgets.QLabel(Form)
self.label_3.setGeometry(QtCore.QRect(40, 100, 351, 31))
font = QtGui.QFont()
font.setFamily("Golos Text")
font.setBold(True)
font.setWeight(75)
self.label_3.setFont(font)
self.label_3.setStyleSheet("color: white;")
self.label_3.setAlignment(QtCore.Qt.AlignCenter)
self.label_3.setObjectName("label_3")
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.pushButton.setText(_translate("Form", "ЗАКРЫТЬ"))
self.label.setText(_translate("Form", "ПОДОЖДИТE"))
self.label_3.setText(_translate("Form", "Идет загрузка"))
class Progress_UI(QWidget): # класс для инициализации окна прогресса
def __init__(self):
super(Progress_UI, self).__init__()
self.setup_progress()
def setup_progress(self):
self.progress_ui = Ui_Form()
self.progress_ui.setupUi(self)
self.progress_ui.progressBar.setRange(0, 0)
self.setWindowFlags(Qt.FramelessWindowHint |
QtCore.Qt.WindowStaysOnTopHint)
self.progress_ui.pushButton.clicked.connect(self.close)
class ThreadForFunc(QThread):
def __init__(self):
QThread.__init__(self)
def run(self):
print('Start run')
def test_func(self): # собственно, функция, которая должна работать. Пока что после её запуска интерфейс виснет
print('Работает!')
self.num = 0
while True:
print(f'{self.num}')
self.msleep(1000)
self.num += 1
class App(QtWidgets.QMainWindow, Ui_MainWindow):
my_signal = QtCore.pyqtSignal() # my_signal
def __init__(self):
super(App, self).__init__()
self.setupUi(self)
# запускаем нужную функцию по нажатию на кнопку
self.pushButton.clicked.connect(self.ais_start)
self.thread = ThreadForFunc()
self.my_signal.connect(self.thread.test_func) # my_signal.connect()
def show_progress_ui(self):
self.progress_ui = Progress_UI()
self.progress_ui.show()
def ais_start(self):
self.show_progress_ui()
self.thread.start()
QTimer.singleShot(3000, self.my_signal.emit) # my_signal.emit()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
application = App()
application.show()
sys.exit(app.exec_())
Ответы (2 шт):
В отдельном потоке запускается функция run из класса ThreadForFunc, а test_func запускается из главного потока, в котором обрабатываются события UI. Поэтому и зависает UI, пока функция не вернется.
Вероятно, вы хотите запустить из ais_start новый поток, в котором будет вызываться test_func. Тогда нужно делать так:
class ThreadForFunc(QThread):
def __init__(self):
QThread.__init__(self)
def run(self):
print('Start run')
self.test_func()
def test_func(self):
print('Работает!')
self.num = 0
while True:
print(f'{self.num}')
self.msleep(1000)
self.num += 1
class App(QtWidgets.QMainWindow, Ui_MainWindow):
my_signal = QtCore.pyqtSignal()
def __init__(self):
... тут все как раньше
self.thread = ThreadForFunc()
self.my_signal.connect(self.thread.start)
... все так же
def ais_start(self):
self.show_progress_ui()
QTimer.singleShot(3000, self.my_signal.emit)
Т.е. test_func должна вызываться напрямую из run, а сигнал должен запускать поток используя start.
Если я вас правильно понял и вам надо выполнить какую-то функцию в дополнительном потоке, тогда вам нужен другой подход.
void QObject::*moveToThread(QThread targetThread)
Изменяет привязку потока для этого объекта и его дочерних элементов. Объект нельзя переместить, если у него есть родитель. Обработка событий продолжится в targetThread.
Больше https://doc.qt.io/qt-5/qobject.html#moveToThread
import sys
from PyQt5 import QtCore, QtWidgets, QtGui
from PyQt5.Qt import *
class Ui_MainWindow(object): # основное окно
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(224, 240, 291, 111))
self.pushButton.setObjectName("pushButton")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(190, 70, 351, 91))
font = QtGui.QFont()
font.setPointSize(26)
self.label.setFont(font)
self.label.setAlignment(QtCore.Qt.AlignCenter)
self.label.setObjectName("label")
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton.setText(_translate("MainWindow", "PushButton"))
self.label.setText(_translate("MainWindow", "ПЕРВОЕ ОКНО"))
class Ui_Form(object): # окно прогресса
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(431, 262)
font = QtGui.QFont()
font.setPointSize(10)
Form.setFont(font)
Form.setStyleSheet("background-color: #21222D;")
self.pushButton = QtWidgets.QPushButton(Form)
self.pushButton.setGeometry(QtCore.QRect(150, 190, 131, 31))
font = QtGui.QFont()
font.setFamily("Golos Text")
font.setPointSize(9)
font.setBold(True)
font.setWeight(75)
self.pushButton.setFont(font)
self.pushButton.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
self.pushButton.setStyleSheet("background-color: white;\n"
"border: 2px solid white;\n"
"border-radius: 15px;\n"
"color: #21222D;\n"
"")
self.pushButton.setObjectName("pushButton")
self.progressBar = QtWidgets.QProgressBar(Form)
self.progressBar.setGeometry(QtCore.QRect(40, 150, 351, 2))
font = QtGui.QFont()
font.setFamily("Golos Text")
font.setPointSize(9)
font.setBold(True)
font.setWeight(75)
self.progressBar.setFont(font)
self.progressBar.setMouseTracking(False)
self.progressBar.setTabletTracking(False)
self.progressBar.setAcceptDrops(False)
self.progressBar.setLayoutDirection(QtCore.Qt.LeftToRight)
self.progressBar.setAutoFillBackground(False)
self.progressBar.setStyleSheet("QProgressBar{\n"
" background-color: #555661;\n"
" color: #16171E;\n"
" text-align: center;\n"
" max-height: 2.45px;\n"
" min-height: 2.45px;\n"
"}\n"
"\n"
"QProgressBar::chunk{\n"
" background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0 #FFC468, stop:1 #F27F7D);\n"
"}")
self.progressBar.setProperty("value", 24)
self.progressBar.setTextVisible(True)
self.progressBar.setOrientation(QtCore.Qt.Horizontal)
self.progressBar.setInvertedAppearance(False)
self.progressBar.setObjectName("progressBar")
self.label = QtWidgets.QLabel(Form)
self.label.setGeometry(QtCore.QRect(40, 50, 351, 31))
font = QtGui.QFont()
font.setFamily("Golos Text")
font.setPointSize(12)
font.setBold(True)
font.setWeight(75)
self.label.setFont(font)
self.label.setStyleSheet("color: white;")
self.label.setAlignment(QtCore.Qt.AlignCenter)
self.label.setObjectName("label")
self.label_3 = QtWidgets.QLabel(Form)
self.label_3.setGeometry(QtCore.QRect(40, 100, 351, 31))
font = QtGui.QFont()
font.setFamily("Golos Text")
font.setBold(True)
font.setWeight(75)
self.label_3.setFont(font)
self.label_3.setStyleSheet("color: white;")
self.label_3.setAlignment(QtCore.Qt.AlignCenter)
self.label_3.setObjectName("label_3")
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.pushButton.setText(_translate("Form", "ЗАКРЫТЬ"))
self.label.setText(_translate("Form", "ПОДОЖДИТE"))
self.label_3.setText(_translate("Form", "Идет загрузка"))
class Progress_UI(QWidget): # класс для инициализации окна прогресса
def __init__(self):
super(Progress_UI, self).__init__()
self.setup_progress()
def setup_progress(self):
self.progress_ui = Ui_Form()
self.progress_ui.setupUi(self)
self.progress_ui.progressBar.setRange(0, 0)
self.setWindowFlags(Qt.FramelessWindowHint |
QtCore.Qt.WindowStaysOnTopHint)
self.progress_ui.pushButton.clicked.connect(self.close)
class Worker(QObject):
valueChanged = pyqtSignal(int)
finished = pyqtSignal()
def run(self):
print('Работает! thread id ->', int(QThread.currentThreadId()))
self.num = 0
for i in range(1, 101):
self.valueChanged.emit(self.num)
QThread.msleep(100)
self.num += 1
self.finished.emit()
class App(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
super(App, self).__init__()
self.setupUi(self)
# запускаем нужную функцию по нажатию на кнопку
self.pushButton.clicked.connect(self.ais_start)
# Current thread id
print('main id = ', int(QThread.currentThreadId()))
# Start thread update progress bar value
self._thread = QThread(self)
self._worker = Worker()
self._worker.moveToThread(self._thread) # Move to thread to execute
self._worker.finished.connect(self.finished_worker)
self._worker.valueChanged.connect(self.set_value)
def finished_worker(self):
print(f'finished_worker !!!')
self.progress_ui.hide()
def set_value(self, value):
print(f'value:{value}')
def show_progress_ui(self):
self.progress_ui = Progress_UI()
self.progress_ui.show()
def ais_start(self):
self.show_progress_ui()
print('main id -> ', int(QThread.currentThreadId()))
self._thread.start() # Start thread
QTimer.singleShot(1, self._worker.run)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
application = App()
application.show()
sys.exit(app.exec_())
