我正在尝试使用 PyQt6 为我的函数创建一个加载屏幕(有时需要很长时间),并且我正在使用 QThreadPool 将这些函数移动到不同的线程中,这是迄今为止我的代码:
from src.gui.loading import Ui_Form
from PyQt6.QtWidgets import QWidget
from PyQt6.QtCore import QThreadPool, QRunnable, QTimer, QThread
from typing import Callable
class Loading(Ui_Form, QWidget):
def __init__(self,
parent: QWidget | None,
next_widget: QWidget | None,
action: str,
time: int,
task: Callable,
task_len: int,
initial_task: str):
super().__init__()
self.setupUi(self)
self.setParent(parent)
self.parent = parent
self.next_widget = next_widget
self.time = time
self.task = TaskRunner(self, task)
self.current_time = 0
self.tasks_done = 0
self.all_tasks = task_len
self.Task.setText(action)
self.Estimation.setText(f"estimated time: {self.int_to_time(time)}")
self.progressBar.setValue(0)
self.TimeLeft.setText("")
self.Current.setText("")
self.Task.setText("")
self.thread_pool = QThreadPool(self)
self.thread_pool.destroyed.connect(self.quit)
self.run_tasks()
self.task_done(initial_task)
self.timer = QTimer(self)
self.timer.timeout.connect(self.update_time)
self.timer.start(1000)
@staticmethod
def int_to_time(time: int) -> str:
if time >= 3600:
return f"{time / 3600} hours"
elif time >= 60:
return f"{time / 60} minutes"
else:
return f"{time} seconds"
def update_time(self):
self.current_time += 1
self.TimeLeft.setText(self.int_to_time(self.current_time))
def task_done(self, next_task: str = None):
self.tasks_done += 1
if not next_task:
self.Current.setText("finished all tasks, closing window")
self.Tasks.setText(f"{self.tasks_done} out of {self.all_tasks}")
elif self.tasks_done != self.all_tasks:
self.Current.setText(f"currently: {next_task}")
self.Tasks.setText(f"{self.tasks_done} out of {self.all_tasks}")
def quit(self):
self.close()
def closeEvent(self, a0):
self.timer.stop()
self.thread_pool.waitForDone(-1)
self.close()
def run_tasks(self): self.thread_pool.start(self.task)
class TaskRunner(QRunnable):
def __init__(self, parent: Loading | None, task: Callable):
super().__init__()
self.parent = parent
self.task = task
def run(self):
self.task(self.parent)
if self.parent:
self.parent.task_done(None)
虽然这段代码大部分有效,但无论如何我似乎都无法关闭窗口(通过关闭窗口,我的意思是在代码内部,使用函数 self.close() 不起作用)。我尝试调试了一下,发现即使所有任务都完成了, self.thread_pool 似乎也没有关闭。我来自 Rust,在 Rust 中,如果没有函数正在运行,线程池就可以关闭,是否可以在 PyQt6 中做同样的事情?如果没有,那为什么?
顺便说一句,对于任何想要尝试此代码的人,这里是 Ui_Form:
# Form implementation generated from reading ui file 'loading.ui'
#
# Created by: PyQt6 UI code generator 6.6.0
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(836, 302)
Form.setStyleSheet("#centralwidget {background-color:rgba(20, 20, 20, 250);}\n"
"#Form {background-color:rgba(20, 20, 20, 250);}\n"
"QMainWindow{background-color:rgba(20, 20, 20, 250);}\n"
"\n"
"QWidget{\n"
" color:rgba(249, 249, 249, 240);\n"
"}\n"
"\n"
"QMenuBar{background-color:rgba(20, 20, 20, 250);}\n"
"QMenu{background-color:rgba(20, 20, 20, 250);}\n"
"QMenu::item:selected{background-color:rgba(229, 229, 229, 100);}\n"
"QMenuBar::item:selected{background-color:rgba(229, 229, 229, 100);}\n"
"QListWidget{background-color:rgba(20, 20, 20, 250);}\n"
"QSpinBox{background-color:rgba(20, 20, 20, 250);}\n"
"\n"
"QMenu::separator{height:5px; background-color:rgba(191, 191, 191, 100);}\n"
"\n"
"QComboBox {\n"
" color:rgba(193, 193, 193, 250);\n"
" background-color:rgba(29, 29, 29, 250);\n"
" border:none;\n"
"}\n"
"QComboBox QAbstractItemView{\n"
" color:rgba(193, 193, 193, 250);\n"
" background-color:rgba(29, 29, 29, 250);\n"
" border:none;\n"
"}\n"
"QLineEdit {\n"
" color:rgba(193, 193, 193, 250);\n"
" background-color:rgba(29, 29, 29, 250);\n"
" border:none;\n"
"}\n"
"QTextEdit {\n"
" color:rgba(193, 193, 193, 250);\n"
" background-color:rgba(29, 29, 29, 250);\n"
" border:none;\n"
"}\n"
"QProgressBar {\n"
" border: 0px solid grey;\n"
" border-radius: 0px;\n"
" background-color:rgba(255, 255, 255, 0);\n"
" }\n"
"QTableWidget {\n"
" background-color: rgba(29, 29, 29, 250);\n"
" color: rgba(193, 193, 193, 250);\n"
"}\n"
"\n"
"QTableWidget::item {\n"
" padding: 4px;\n"
"}\n"
"\n"
"QTableWidget::item:selected {\n"
" background-color: rgba(50, 50, 50, 250);\n"
"}\n"
"\n"
"QTableWidget::item:focus {\n"
" background-color: rgba(70, 70, 70, 250);\n"
" outline: none;\n"
"}\n"
"\n"
"QHeaderView::section {\n"
" background-color: rgba(50, 50, 50, 250);\n"
" color: rgba(193, 193, 193, 250);\n"
" padding: 4px;\n"
" border: none;\n"
"}\n"
"\n"
"QHeaderView {\n"
" background-color: rgba(50, 50, 50, 250);\n"
" color: rgba(193, 193, 193, 250);\n"
" padding: 4px;\n"
" border: none;\n"
"}\n"
"\n"
"QHeaderView::section:checked {\n"
" background-color: rgba(70, 70, 70, 250);\n"
"}\n"
" QTableView QTableCornerButton::section {\n"
" background-color: rgba(50, 50, 50, 250);\n"
" }\n"
"\n"
"QPushButton{background-color:rgba(59, 59, 59, 250);}\n"
"QPushButton:hover{background-color:rgba(107, 107, 107, 250);}")
self.verticalLayout = QtWidgets.QVBoxLayout(Form)
self.verticalLayout.setObjectName("verticalLayout")
self.Task = QtWidgets.QLabel(parent=Form)
font = QtGui.QFont()
font.setFamily("Secular One")
font.setPointSize(20)
self.Task.setFont(font)
self.Task.setText("")
self.Task.setObjectName("Task")
self.verticalLayout.addWidget(self.Task, 0, QtCore.Qt.AlignmentFlag.AlignHCenter)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.Estimation = QtWidgets.QLabel(parent=Form)
font = QtGui.QFont()
font.setFamily("Secular One")
font.setPointSize(20)
self.Estimation.setFont(font)
self.Estimation.setObjectName("Estimation")
self.horizontalLayout.addWidget(self.Estimation)
self.TimeLeft = QtWidgets.QLabel(parent=Form)
font = QtGui.QFont()
font.setFamily("Secular One")
font.setPointSize(20)
self.TimeLeft.setFont(font)
self.TimeLeft.setText("")
self.TimeLeft.setObjectName("TimeLeft")
self.horizontalLayout.addWidget(self.TimeLeft, 0, QtCore.Qt.AlignmentFlag.AlignRight)
self.verticalLayout.addLayout(self.horizontalLayout)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.Current = QtWidgets.QLabel(parent=Form)
font = QtGui.QFont()
font.setFamily("Secular One")
font.setPointSize(20)
self.Current.setFont(font)
self.Current.setObjectName("Current")
self.horizontalLayout_2.addWidget(self.Current)
self.Tasks = QtWidgets.QLabel(parent=Form)
font = QtGui.QFont()
font.setFamily("Secular One")
font.setPointSize(20)
self.Tasks.setFont(font)
self.Tasks.setObjectName("Tasks")
self.horizontalLayout_2.addWidget(self.Tasks)
self.verticalLayout.addLayout(self.horizontalLayout_2)
self.progressBar = QtWidgets.QProgressBar(parent=Form)
font = QtGui.QFont()
font.setFamily("Secular One")
font.setPointSize(20)
self.progressBar.setFont(font)
self.progressBar.setMinimum(0)
self.progressBar.setProperty("value", 0)
self.progressBar.setObjectName("progressBar")
self.verticalLayout.addWidget(self.progressBar)
self.Error = QtWidgets.QLabel(parent=Form)
font = QtGui.QFont()
font.setFamily("Secular One")
font.setPointSize(20)
self.Error.setFont(font)
self.Error.setStyleSheet("color: red;")
self.Error.setText("")
self.Error.setObjectName("Error")
self.verticalLayout.addWidget(self.Error)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.Estimation.setText(_translate("Form", "estimated time:"))
self.Current.setText(_translate("Form", "current task:"))
self.Tasks.setText(_translate("Form", "task x out of y"))
在我的例子中使用 QRunnable 是一个错误。 QRunnable 的想法是它比 QThread 更轻、更简单,使其更容易在需要运行大量线程的项目中使用。因为我只想运行一个单边线程,所以使用 QThread 更简单(并解决问题)。我刚刚删除了 self.thread_pool 并将
class TaskRunner(QRunnable)
变成了 class TaskRunner(QThread)
,而不是运行 self.thread_pool.start(self.task)
我可以只运行 self.task.start
。现在我可以随时删除线程,但代码保持(几乎)相同。