在具有复杂计算过程的Qt中显示进度条

问题描述 投票:0回答:1

我正在构建一个应用程序,让用户导出他/她的工作。这是一个计算量很大的过程,持续一分钟左右,在此过程中,我想显示一个进度条(并使UI的其余部分无响应)。

我已经尝试了以下实现,对于非计算上昂贵的后台进程(例如,等待0.1 s),它可以很好地工作。但是,对于CPU繁重的进程,UI会变得非常缓慢且无响应(但不会完全无响应)。

知道如何解决这个问题?

import sys
import time

from PySide2 import QtCore
from PySide2.QtCore import Qt
import PySide2.QtWidgets as QtWidgets


class MainWindow(QtWidgets.QMainWindow):
    """Main window, with one button for exporting stuff"""

    def __init__(self, parent=None):
        super().__init__(parent)
        central_widget = QtWidgets.QWidget(self)
        layout = QtWidgets.QHBoxLayout(self)
        button = QtWidgets.QPushButton("Press me...")
        button.clicked.connect(self.export_stuff)
        layout.addWidget(button)
        central_widget.setLayout(layout)
        self.setCentralWidget(central_widget)

    def export_stuff(self):
        """Opens dialog and starts exporting"""
        some_window = MyExportDialog(self)
        some_window.exec_()


class MyAbstractExportThread(QtCore.QThread):
    """Base export thread"""
    change_value = QtCore.Signal(int)

    def run(self):
        cnt = 0
        while cnt < 100:
            cnt += 1
            self.operation()
            self.change_value.emit(cnt)

    def operation(self):
        pass


class MyExpensiveExportThread(MyAbstractExportThread):

    def operation(self):
        """Something that takes a lot of CPU power"""
        some_val = 0
        for i in range(1000000):
            some_val += 1


class MyInexpensiveExportThread(MyAbstractExportThread):

    def operation(self):
        """Something that doesn't take a lot of CPU power"""
        time.sleep(.1)


class MyExportDialog(QtWidgets.QDialog):
    """Dialog which does some stuff, and shows its progress"""

    def __init__(self, parent=None):
        super().__init__(parent, Qt.WindowCloseButtonHint)
        self.setWindowTitle("Exporting...")
        layout = QtWidgets.QHBoxLayout()
        self.progress_bar = self._create_progress_bar()
        layout.addWidget(self.progress_bar)
        self.setLayout(layout)
        self.worker = MyInexpensiveExportThread()  # Works fine
        # self.worker = MyExpensiveExportThread()  # Super laggy
        self.worker.change_value.connect(self.progress_bar.setValue)
        self.worker.start()
        self.worker.finished.connect(self.close)

    def _create_progress_bar(self):
        progress_bar = QtWidgets.QProgressBar(self)
        progress_bar.setMinimum(0)
        progress_bar.setMaximum(100)
        return progress_bar


if __name__ == "__main__":
    app = QtWidgets.QApplication()
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())
python python-multithreading qthread pyside2
1个回答
0
投票

您应该使用asyncqt,它是PySide2的quamash衍生产品。我将QThread实现保留在您的代码中,并使用QEventLoop进行了修订。作为最终解决方案,您应该考虑使用run_in_executor来更改QThread实现,如asyncqt github页面中所示。

import sys
import time
import asyncio
from PySide2.QtCore import (Qt, Signal, Slot, QObject, QThread)
from PySide2.QtWidgets import (QApplication, QProgressBar, QWidget, QHBoxLayout, QPushButton, QMainWindow, QDialog)
from asyncqt import (QEventLoop, QThreadExecutor)


class MainWindow(QMainWindow):
    """Main window, with one button for exporting stuff"""

    def __init__(self, parent=None):
        super().__init__(parent)
        central_widget = QWidget(self)
        layout = QHBoxLayout(self)
        button = QPushButton("Press me...")
        button.clicked.connect(self.export_stuff)
        layout.addWidget(button)
        central_widget.setLayout(layout)
        self.setCentralWidget(central_widget)

    def export_stuff(self):
        """Opens dialog and starts exporting"""
        some_window = MyExportDialog(self)
        some_window.exec_()


class MyAbstractExportThread(QThread):
    """Base export thread"""
    change_value = Signal(int)
    loop = asyncio.get_event_loop()

    def run(self):
        cnt = 0
        while cnt < 100:
            cnt += 1
            self.loop.run_until_complete(self.operation())
            self.change_value.emit(cnt)

    @asyncio.coroutine
    def operation(self):
        pass


class MyExpensiveExportThread(MyAbstractExportThread):

    @asyncio.coroutine
    def operation(self):
        """Something that takes a lot of CPU power"""
        some_val = 0
        for i in range(10000000):
            some_val += 1


class MyInexpensiveExportThread(MyAbstractExportThread):

    def operation(self):
        """Something that doesn't take a lot of CPU power"""
        time.sleep(.1)


class MyExportDialog(QDialog):
    """Dialog which does some stuff, and shows its progress"""

    def __init__(self, parent=None):
        super().__init__(parent, Qt.WindowCloseButtonHint)
        self.loop = asyncio.get_event_loop()
        self.setWindowTitle("Exporting...")
        layout = QHBoxLayout()
        self.progress_bar = self._create_progress_bar()
        layout.addWidget(self.progress_bar)
        self.setLayout(layout)
        # self.worker = MyInexpensiveExportThread()  # Works fine
        self.worker = MyExpensiveExportThread()  # Super laggy
        self.worker.change_value.connect(self.set_progressbar)
        self.worker.finished.connect(self.close)

        with QThreadExecutor(1) as qt_thread_executor:
            loop.run_in_executor(qt_thread_executor, self.worker.start)

    def _create_progress_bar(self):
        progress_bar = QProgressBar(self)
        progress_bar.setMinimum(0)
        progress_bar.setMaximum(100)
        return progress_bar

    @Slot(int)
    def set_progressbar(self, value):
        self.loop.call_soon_threadsafe(self.progress_bar.setValue, value)


if __name__ == "__main__":
    app = QApplication()
    loop = QEventLoop(app)
    asyncio.set_event_loop(loop)
    window = MainWindow()
    window.show()
    loop.run_forever(sys.exit(app.exec_()))
© www.soinside.com 2019 - 2024. All rights reserved.