PyQt5:使用 QThread 弹出进度条 [英] PyQt5: pop-up progressbar using QThread

查看:267
本文介绍了PyQt5:使用 QThread 弹出进度条的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我如何在弹出窗口中实现一个进度条,以监控来自所谓的 Worker 类(即时间/CPU- 消耗任务)通过 QThread?

我查看了无数示例和教程,但进度条显示在弹出窗口中这一事实似乎让一切变得更加困难.我相信我想要的是一件相当简单的事情,但我一直在失败,而且我的想法也用完了.

我有一个我想要实现的示例,它基于

我有一系列问题:

  1. 为什么会崩溃?
  2. 为什么它在调试过程中起作用?
  3. 我是否应该放弃并实现进度条(锚定)应用程序的主窗口?
  4. 我过去已经实现了类似的东西,但没有线程化:在工作函数(即 CPU 消耗函数)的循环中,我必须添加 QApplication.processEvents() 以便在每个迭代进度条被有效更新.以这种方式做事显然是次优的.与我现在正在努力实现的目标相比,它仍然是更好的替代方案吗?

如果我遗漏了一些明显的东西,或者已经在某处得到了回答(重复),请原谅:我无法找到这个问题的答案.预先非常感谢您.

解决方案

说明:

要理解这个问题,你必须知道以下几点:

self.main_window_button.clicked.connect(PopUpProgressB)

相当于:

self.main_window_button.clicked.connect(foo)# ...定义 foo():弹出进度B()

当按下按钮时,会创建一个没有生命周期的 PopUpProgressB 对象,就像执行foo"函数几乎是瞬时的一样,因此弹出窗口将在很短的时间内显示和隐藏时间.

解决方案:

这个想法是弹出窗口有一个范围,允许它有一个足够大的生命周期来显示它应该对类属性弹出对象进行的进度.

# ...self.main_window_button = QPushButton("开始")self.popup = PopUpProgressB()self.main_window_button.clicked.connect(self.popup.show)self.h_box.addWidget(self.main_window_button)# ...

为了不显示您必须删除对 PopUpProgressB 的 show() 方法的调用:

class PopUpProgressB(QWidget):def __init__(self):super().__init__()# ...self.setWindowTitle('进度条')# self.show() # <--- 删除这一行self.obj = Worker()# ...

<小时>

既然我已经解释了你的问题的失败,我就回答你的问题:

  1. 为什么会崩溃? 当弹出对象被删除时,创建的 QThread 也被删除但 Qt 访问不再分配的内存(核心转储)导致应用程序关闭而不抛出任何例外.

  2. 为什么它在调试过程中起作用?许多像 PyCharm 这样的 IDE 不处理 Qt 错误,所以恕我直言,当它们出现此类错误时,它们会在终端/CMD 中执行它们的代码,以便例如,当我执行您获得的代码时:

    QThread:线程仍在运行时被销毁中止(核心转储)

  3. 我应该放弃并在应用程序的主窗口中实现进度条(锚定)吗?不.

  4. 我过去已经实现了类似的东西,但没有线程化:在工作函数(即 CPU 消耗函数)的循环中,我必须添加 QApplication.processEvents() 以便在每次迭代时进度条得到有效更新.以这种方式做事显然是次优的.它仍然是我现在想要实现的更好的替代方案吗? 如果有更好的替代方案,请不要使用 QApplication::processEvents(),在这种情况下,线程是最好的,因为它使主线程不那么忙.

<小时>

最后,初学者在 Qt 中报告的许多错误都涉及变量的范围,因此我建议您分析每个变量应该是多少,例如,如果您希望一个对象与然后类使该变量成为类的属性,如果您只在方法中使用它,那么它只是一个局部变量,等等.

How can I implement a progress bar in a pop-up window that monitors the progress of a running function from a so-called Worker class (i.e. time/CPU-consuming task) by means of a QThread?

I have checked countless examples and tutorials, but the fact that the progressbar shows up in a pop-up window seems to make everything harder. I believe what I want is a rather simple thing but I keep failing at it and I ran out of ideas.

I have an example of what I am trying to achieve, which is based on this answer:

import sys
import time
from PyQt5.QtCore import QThread, pyqtSignal, QObject, pyqtSlot
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget, QHBoxLayout, QProgressBar, QVBoxLayout


class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Widget")
        self.h_box = QHBoxLayout(self)
        self.main_window_button = QPushButton("Start")
        self.main_window_button.clicked.connect(PopUpProgressB)
        self.h_box.addWidget(self.main_window_button)
        self.setLayout(self.h_box)
        self.show()


class Worker(QObject):
    finished = pyqtSignal()
    intReady = pyqtSignal(int)

    @pyqtSlot()
    def proc_counter(self):  # A slot takes no params
        for i in range(1, 100):
            time.sleep(1)
            self.intReady.emit(i)

        self.finished.emit()


class PopUpProgressB(QWidget):

    def __init__(self):
        super().__init__()
        self.pbar = QProgressBar(self)
        self.pbar.setGeometry(30, 40, 500, 75)
        self.layout = QVBoxLayout()
        self.layout.addWidget(self.pbar)
        self.setLayout(self.layout)
        self.setGeometry(300, 300, 550, 100)
        self.setWindowTitle('Progress Bar')
        self.show()

        self.obj = Worker()
        self.thread = QThread()
        self.obj.intReady.connect(self.on_count_changed)
        self.obj.moveToThread(self.thread)
        self.obj.finished.connect(self.thread.quit)
        self.thread.started.connect(self.obj.proc_counter)
        self.thread.start()

    def on_count_changed(self, value):
        self.pbar.setValue(value)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_window = MainWindow()
    sys.exit(app.exec_())

When I run the latter (e.g. in PyCharm Community 2019.3), the program crashes and I don't get any clear error message.

When I debug it though, it looks like it works, as I am able to see what I intended to achieve:

I have a series of questions:

  1. Why does it crash?
  2. Why does it work during debugging?
  3. Sould I just give up and implement the progress bar (anchored) in the main window of the app?
  4. I already implemented a similar thing in the past but without threading: within the loop of the worker function (i.e. CPU-consuming function) I had to add QApplication.processEvents() so that at each iteration the progressbar was effectively updated. It is apparently suboptimal to do things this way. Is it still a better alternative to what I am trying to achieve now?

I beg your pardon if there is something obvious that I am missing, or if this has been already be answered somewehere (duplicate): I am unable to find the answer to this problem. Thank you very much in advance.

解决方案

Explanation:

To understand the problem you must know that the following:

self.main_window_button.clicked.connect(PopUpProgressB)

Equivalent to:

self.main_window_button.clicked.connect(foo)
# ...
def foo():
    PopUpProgressB()

Where it is observed that when pressing the button a PopUpProgressB object is created that does not have a life cycle just like the execution of the "foo" function that is practically instantaneous so the popup will be shown and hidden in a very short time.

Solution:

The idea is that the popup has a scope that allows it to have a life cycle large enough to show the progress for it should be made to the class attribute popup object.

# ...
self.main_window_button = QPushButton("Start")
self.popup = PopUpProgressB()
self.main_window_button.clicked.connect(self.popup.show)
self.h_box.addWidget(self.main_window_button)
# ...

And so that it doesn't show you must remove the call to the show() method of PopUpProgressB:

class PopUpProgressB(QWidget):
    def __init__(self):
        super().__init__()
        # ...
        self.setWindowTitle('Progress Bar')
        # self.show() # <--- remove this line
        self.obj = Worker()
        # ...


Since I already explained the failure of your problem I will answer your questions:

  1. Why does it crash? When the popup object is deleted, the created QThread is also deleted but Qt accesses no longer allocated memory (core dumped) causing the application to close without throwing any exceptions.

  2. Why does it work during debugging? Many IDEs like PyCharm do not handle Qt errors, so IMHO recommends that when they have such errors they execute their code in the terminal/CMD, for example when I execute your code I obtained:

    QThread: Destroyed while thread is still running
    Aborted (core dumped)
    

  3. Should I just give up and implement the progress bar (anchored) in the main window of the app? No.

  4. I already implemented a similar thing in the past but without threading: within the loop of the worker function (i.e. CPU-consuming function) I had to add QApplication.processEvents() so that at each iteration the progressbar was effectively updated. It is apparently suboptimal to do things this way. Is it still a better alternative to what I am trying to achieve now? Do not use QApplication::processEvents() if there are better alternatives, in this case the threads is the best since it makes the main thread less busy.


Finally, many of the errors that beginners report in Qt refer to the scope of the variables so I recommend you analyze how much it should be for each variable, for example if you want an object to live the same as the class then make that variable is an attribute of the class, if instead you only use it in a method then it is only a local variable, etc.

这篇关于PyQt5:使用 QThread 弹出进度条的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆