PYQT延迟功能的计算而无需阻止主线程

发布于 2025-02-07 22:26:29 字数 680 浏览 3 评论 0原文

我继承了一个构建类似的GUI代码: 任何按钮信号都会触发一个插槽,这些插槽然后调用外部进程以接收信息并等到该过程完成,然后插槽继续进行。问题是,此外部过程需要0.5到60秒,而GUI冻结了。我正在努力寻找一种很好的方法,将此过程分开为其他线程或QProcess(这样我就不会阻止主事件循环),然后返回并继续从同一点继续使用相关的插槽(或函数)从该外部慢速过程中收到的信息。发电机似乎应该放在这里,但是我正在努力弄清楚如何重组代码,这样就可以了。 有什么建议或想法吗?是否有QT的方法来“产生”功能,直到该过程完成然后继续该功能?

当前结构的PSUDO代码:

    button1.clicked.connect(slot1)
    button2.clicked.connect(slot2)
    
    def slot1():
        status = subprocess.run("external proc") # this is blocking
        ...
        ...
        return

    def slot2():
        status = subprocess.run("external proc") # this is blocking
        ...
        ...
        return


I've inhereted a GUI code which is structured something like this:
any button signal triggers a slot, those slots then call an external process to receive information and wait until that process finishes, then the slot proceeds. the issue is, this external process takes between 0.5 to 60 seconds, and in that time the GUI freezes. i'm struggling to find a good way to seperate this process call to a different thread or QProcess (that way i will not block the main event loop) and then return and continue the relevent slot (or function) from that same point with the information received from that external slow process. generators seem like something that should go here, but im struggling to figure how to restructure the code so this will work.
any suggestions or ideas? is there a Qt way to "yield" a function until that process completes and then continue that function?

Psudo code of the current structure:

    button1.clicked.connect(slot1)
    button2.clicked.connect(slot2)
    
    def slot1():
        status = subprocess.run("external proc") # this is blocking
        ...
        ...
        return

    def slot2():
        status = subprocess.run("external proc") # this is blocking
        ...
        ...
        return


如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

柳絮泡泡 2025-02-14 22:26:29

这是我在评论中提到的示例的代码:

class MainWindow(QMainWindow, ui_MainWindow):
    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)
        ui_MainWindow.__init__(self)
        self.setupUi(self)
    
        self.button_1.clicked.connect(lambda: self.threaded_wait(1))
        self.button_5.clicked.connect(lambda: self.threaded_wait(5))
        self.button_10.clicked.connect(lambda: self.threaded_wait(10))
    
        #Start a timer that executes every 0.5 seconds
        self.timer = QtCore.QBasicTimer()                                               
        self.timer.start(500, self)
    
        #INIT variables
        self.results = {}
        self.done = False
   
    def timerEvent(self, event):
        #Executes every 500msec.
        if self.done:
            print(self.results)
            self.done = False
    
   
    def threaded_wait(self, time_to_wait):
        self.done = False
        new_thread = threading.Thread(target=self.actual_wait, args=(time_to_wait,self.sender().objectName()))
        new_thread.start()
    
    def actual_wait(self, time_to_wait: int, button_name):
        print(f"Button {button_name} Pressed:\nSleeping for {int(time_to_wait)} seconds")

        time_passed = 0
    
        for i in range(0, time_to_wait):
            print(int( time_to_wait - time_passed))
            time.sleep(1)
            time_passed = time_passed + 1
    
        self.results[button_name] = [1,2,3,4,5]
        self.done = True
        print("Done!")

Here is the code with the example I was mentioning in the comments:

class MainWindow(QMainWindow, ui_MainWindow):
    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)
        ui_MainWindow.__init__(self)
        self.setupUi(self)
    
        self.button_1.clicked.connect(lambda: self.threaded_wait(1))
        self.button_5.clicked.connect(lambda: self.threaded_wait(5))
        self.button_10.clicked.connect(lambda: self.threaded_wait(10))
    
        #Start a timer that executes every 0.5 seconds
        self.timer = QtCore.QBasicTimer()                                               
        self.timer.start(500, self)
    
        #INIT variables
        self.results = {}
        self.done = False
   
    def timerEvent(self, event):
        #Executes every 500msec.
        if self.done:
            print(self.results)
            self.done = False
    
   
    def threaded_wait(self, time_to_wait):
        self.done = False
        new_thread = threading.Thread(target=self.actual_wait, args=(time_to_wait,self.sender().objectName()))
        new_thread.start()
    
    def actual_wait(self, time_to_wait: int, button_name):
        print(f"Button {button_name} Pressed:\nSleeping for {int(time_to_wait)} seconds")

        time_passed = 0
    
        for i in range(0, time_to_wait):
            print(int( time_to_wait - time_passed))
            time.sleep(1)
            time_passed = time_passed + 1
    
        self.results[button_name] = [1,2,3,4,5]
        self.done = True
        print("Done!")

enter image description here

無心 2025-02-14 22:26:29

您可以使用Qthread。使用QThread,您可以将参数传递给MainWindow中的功能,并具有信号机制。

Here is a source that explains how to use Qthread:

https://realpython.com/python-pyqt- 我认为, qthread/

如果您阅读了伴侣,它将对您有所帮助。页面上有一个示例GUI,我将其写给您(您可以运行):

from PyQt5.QtCore import QObject, QThread, pyqtSignal
from PyQt5.QtWidgets import QMainWindow
import time
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
    QApplication,
    QLabel,
    QMainWindow,
    QPushButton,
    QVBoxLayout,
    QWidget,
)
import sys
# Snip...

# Step 1: Create a worker class
#
class Worker(QObject):
    finished = pyqtSignal()
    progress = pyqtSignal(int)

    def run(self):
        """Long-running task."""
        for i in range(5):
            time.sleep(1)
            self.progress.emit(i + 1)
        self.finished.emit()

class Window(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.clicksCount = 0
        self.setupUi()

    def setupUi(self):
        self.setWindowTitle("Freezing GUI")
        self.resize(300, 150)
        self.centralWidget = QWidget()
        self.setCentralWidget(self.centralWidget)
        # Create and connect widgets
        self.clicksLabel = QLabel("Counting: 0 clicks", self)
        self.clicksLabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.stepLabel = QLabel("Long-Running Step: 0")
        self.stepLabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.countBtn = QPushButton("Click me!", self)
        self.countBtn.clicked.connect(self.countClicks)
        self.longRunningBtn = QPushButton("Long-Running Task!", self)
        self.longRunningBtn.clicked.connect(self.runLongTask)
        # Set the layout
        layout = QVBoxLayout()
        layout.addWidget(self.clicksLabel)
        layout.addWidget(self.countBtn)
        layout.addStretch()
        layout.addWidget(self.stepLabel)
        layout.addWidget(self.longRunningBtn)
        self.centralWidget.setLayout(layout)

    def countClicks(self):
        self.clicksCount += 1
        self.clicksLabel.setText(f"Counting: {self.clicksCount} clicks")

    def reportProgress(self, n):
        self.stepLabel.setText(f"Long-Running Step: {n}")

    def runLongTask(self):
        # Step 2: Create a QThread object
        self.thread = QThread()
        # Step 3: Create a worker object
        self.worker = Worker()
        # Step 4: Move worker to the thread
        self.worker.moveToThread(self.thread)
        # Step 5: Connect signals and slots
        self.thread.started.connect(self.worker.run)
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)
        self.worker.progress.connect(self.reportProgress)
        # Step 6: Start the thread
        self.thread.start()

        # Final resets
        self.longRunningBtn.setEnabled(False)
        self.thread.finished.connect(
            lambda: self.longRunningBtn.setEnabled(True)
        )
        self.thread.finished.connect(
            lambda: self.stepLabel.setText("Long-Running Step: 0")
        )



app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec())

You can use QThread. With Qthread you can pass arguments to a function in mainWindow with signal mechanism.

Here is a source that explains how to use Qthread:

https://realpython.com/python-pyqt-qthread/

if you read the soruce it will be helpfull to you, i think. And there is a sample gui in the page, i write it down to you(you can run it):

from PyQt5.QtCore import QObject, QThread, pyqtSignal
from PyQt5.QtWidgets import QMainWindow
import time
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
    QApplication,
    QLabel,
    QMainWindow,
    QPushButton,
    QVBoxLayout,
    QWidget,
)
import sys
# Snip...

# Step 1: Create a worker class
#
class Worker(QObject):
    finished = pyqtSignal()
    progress = pyqtSignal(int)

    def run(self):
        """Long-running task."""
        for i in range(5):
            time.sleep(1)
            self.progress.emit(i + 1)
        self.finished.emit()

class Window(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.clicksCount = 0
        self.setupUi()

    def setupUi(self):
        self.setWindowTitle("Freezing GUI")
        self.resize(300, 150)
        self.centralWidget = QWidget()
        self.setCentralWidget(self.centralWidget)
        # Create and connect widgets
        self.clicksLabel = QLabel("Counting: 0 clicks", self)
        self.clicksLabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.stepLabel = QLabel("Long-Running Step: 0")
        self.stepLabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.countBtn = QPushButton("Click me!", self)
        self.countBtn.clicked.connect(self.countClicks)
        self.longRunningBtn = QPushButton("Long-Running Task!", self)
        self.longRunningBtn.clicked.connect(self.runLongTask)
        # Set the layout
        layout = QVBoxLayout()
        layout.addWidget(self.clicksLabel)
        layout.addWidget(self.countBtn)
        layout.addStretch()
        layout.addWidget(self.stepLabel)
        layout.addWidget(self.longRunningBtn)
        self.centralWidget.setLayout(layout)

    def countClicks(self):
        self.clicksCount += 1
        self.clicksLabel.setText(f"Counting: {self.clicksCount} clicks")

    def reportProgress(self, n):
        self.stepLabel.setText(f"Long-Running Step: {n}")

    def runLongTask(self):
        # Step 2: Create a QThread object
        self.thread = QThread()
        # Step 3: Create a worker object
        self.worker = Worker()
        # Step 4: Move worker to the thread
        self.worker.moveToThread(self.thread)
        # Step 5: Connect signals and slots
        self.thread.started.connect(self.worker.run)
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)
        self.worker.progress.connect(self.reportProgress)
        # Step 6: Start the thread
        self.thread.start()

        # Final resets
        self.longRunningBtn.setEnabled(False)
        self.thread.finished.connect(
            lambda: self.longRunningBtn.setEnabled(True)
        )
        self.thread.finished.connect(
            lambda: self.stepLabel.setText("Long-Running Step: 0")
        )



app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec())
千秋岁 2025-02-14 22:26:29

通常,我要做的是按下按钮运行一个函数,该功能启动线程为我完成工作。

在我的示例中,我有3个按钮。一个等待一秒钟,另一个等待5,另一个等待10。

我将按钮插槽单击时连接threeed_wait(),并且我使用lambda,因为我想通过该方法将整数参数传递到多长时间等待(在此示例中等待只是假处理时间)。

然后,我拥有实际_WAIT()方法,即实际等待的代码,该代码由线程执行。由于有一个线程运行该代码,因此启动线程后,主GUI事件循环立即退出螺纹_WAIT()方法,并且可以继续其事件循环,

class MainWindow(QMainWindow, ui_MainWindow):
    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)
        ui_MainWindow.__init__(self)
        self.setupUi(self)
    
        self.button_1.clicked.connect(lambda: self.threaded_wait(1))
        self.button_5.clicked.connect(lambda: self.threaded_wait(5))
        self.button_10.clicked.connect(lambda: self.threaded_wait(10))
    
    def threaded_wait(self, time_to_wait):
        new_thread = threading.Thread(target=self.actual_wait, args=(time_to_wait,))
        new_thread.start()
    
    def actual_wait(self, time_to_wait: int):
        print(f"Sleeping for {int(time_to_wait)} seconds")

        time_passed = 0
    
        for i in range(0, time_to_wait):
            print(int( time_to_wait - time_passed))
            time.sleep(1)
            time_passed = time_passed + 1
    
        print("Done!")

从而阻止我的GUI冻结。

编辑:

对不起,对于您的问题的第二部分,如果您想等待线程完成之前完成其他操作,则可以使用这样的标志:

def actual_wait(self, time_to_wait: int):
    print(f"Sleeping for {int(time_to_wait)} seconds")

    ....
    
    self.DONE = True

并检查self.done flag无论您在哪里需要它。
这取决于您通过等待完成的意思。
我认为,如果您使用Qthread,则在完成线程后也可以发出信号,并将该信号连接到此后的任何插槽,但是我没有使用Qthread。

Usually what I do is have the button press run a function that launches a thread to do the work for me.

In my example I have 3 buttons. One that waits for one second, another that waits for 5, and another that waits for 10.

I connect the button slots when they are clicked to threaded_wait() and I use lambda because I want to pass that method an integer argument on how long to wait for (Waiting in this example is just fake processing time).

Then I have the method actual_wait() which is the code that is actually waiting, which is being executed by the thread. Since there is a thread running that code, the main GUI event loop exits the threaded_wait() method right after starting the thread and it is allowed to continue it's event loop

class MainWindow(QMainWindow, ui_MainWindow):
    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)
        ui_MainWindow.__init__(self)
        self.setupUi(self)
    
        self.button_1.clicked.connect(lambda: self.threaded_wait(1))
        self.button_5.clicked.connect(lambda: self.threaded_wait(5))
        self.button_10.clicked.connect(lambda: self.threaded_wait(10))
    
    def threaded_wait(self, time_to_wait):
        new_thread = threading.Thread(target=self.actual_wait, args=(time_to_wait,))
        new_thread.start()
    
    def actual_wait(self, time_to_wait: int):
        print(f"Sleeping for {int(time_to_wait)} seconds")

        time_passed = 0
    
        for i in range(0, time_to_wait):
            print(int( time_to_wait - time_passed))
            time.sleep(1)
            time_passed = time_passed + 1
    
        print("Done!")

This prevents my GUI from freezing up.

enter image description here

EDIT:

Sorry as for the second part of your question, if you want to wait for the thread to finish before doing something else, you can use a flag like this:

def actual_wait(self, time_to_wait: int):
    print(f"Sleeping for {int(time_to_wait)} seconds")

    ....
    
    self.DONE = True

And check that self.DONE flag wherever you need it.
It kind of depends what you mean by wait for it to complete.
I think if you use QThread you can also emit a signal when the thread is done and connect that signal to whatever slot after that, but I haven't used QThread.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文