【问题标题】:PyQt: QThreadPool with QRunnables taking time to quitPyQt:带有 QRunnables 的 QThreadPool 需要时间退出
【发布时间】:2018-03-12 16:22:07
【问题描述】:

我有一个类创建 QRunnables 并在 QThreadPool 实例中启动它们。

我的线程运行良好,但如果用户想退出应用程序,应用程序需要很长时间才能停止。当然是因为发起请求需要时间。

这是我如何使用 QThreadPool、QRunnables 的 sn-p 代码:

import sys
from PyQt5.Qt import QThreadPool, QApplication, QWidget, QVBoxLayout
from PyQt5.Qt import QTimer, QObject, QPushButton, QLabel
from PyQt5.Qt import QRunnable


class BackendQRunnable(QRunnable):
    """
        Class who create a QThread to trigger requests
    """

    def __init__(self, task):
        super(BackendQRunnable, self).__init__()
        self.task = task

    def run(self):
        """
        Run the QRunnable. Trigger actions depending on the selected task

        """

        # Here I make long requests
        if 'user' in self.task:
            self.query_user_data()
        elif 'host' in self.task:
            self.query_hosts_data()
        elif 'service' in self.task:
            self.query_services_data()
        elif 'alignakdaemon' in self.task:
            self.query_daemons_data()
        elif 'livesynthesis' in self.task:
            self.query_livesynthesis_data()
        elif 'history' in self.task:
            self.query_history_data()
        elif 'notifications' in self.task:
            self.query_notifications_data()
        else:
            pass

    @staticmethod
    def query_user_data():
        """
        Launch request for "user" endpoint

        """

        print('Query user data')

    @staticmethod
    def query_hosts_data():
        """
        Launch request for "host" endpoint

        """

        print('Query hosts')

    @staticmethod
    def query_services_data():
        """
        Launch request for "service" endpoint

        """

        print("Query services")

    @staticmethod
    def query_daemons_data():
        """
        Launch request for "alignakdaemon" endpoint

        """

        print('Query daemons')

    @staticmethod
    def query_livesynthesis_data():
        """
        Launch request for "livesynthesis" endpoint

        """

        print('query livesynthesis')

    @staticmethod
    def query_history_data():
        """
        Launch request for "history" endpoint but only for hosts in "data_manager"

        """

        print('Query history')

    @staticmethod
    def query_notifications_data():
        """
        Launch request for "history" endpoint but only for notifications of current user

        """

        print('Query notifications')


class ThreadManager(QObject):
    """
        Class who create BackendQRunnable to periodically request on a Backend
    """

    def __init__(self, parent=None):
        super(ThreadManager, self).__init__(parent)
        self.backend_thread = BackendQRunnable(self)
        self.pool = QThreadPool.globalInstance()
        self.tasks = self.get_tasks()

    def start(self):
        """
        Start ThreadManager

        """

        print("Start backend Manager...")

        # Make a first request
        self.create_tasks()

        # Then request periodically
        timer = QTimer(self)
        timer.setInterval(10000)
        timer.start()
        timer.timeout.connect(self.create_tasks)

    @staticmethod
    def get_tasks():
        """
        Return the tasks to run in BackendQRunnable

        :return: tasks to run
        :rtype: list
        """

        return [
            'notifications', 'livesynthesis', 'alignakdaemon', 'history', 'service', 'host', 'user',
        ]

    def create_tasks(self):
        """
        Create tasks to run

        """

        for cur_task in self.tasks:
            backend_thread = BackendQRunnable(cur_task)

            # Add task to QThreadPool
            self.pool.start(backend_thread)

    def exit_pool(self):
        """
        Exit all BackendQRunnables and delete QThreadPool

        """

        # When trying to quit, the application takes a long time to stop
        self.pool.globalInstance().waitForDone()
        self.pool.deleteLater()

        sys.exit(0)


if __name__ == '__main__':
    app = QApplication(sys.argv)

    thread_manager = ThreadManager()
    thread_manager.start()

    layout = QVBoxLayout()

    label = QLabel("Start")
    button = QPushButton("DANGER!")
    button.pressed.connect(thread_manager.exit_pool)

    layout.addWidget(label)
    layout.addWidget(button)

    w = QWidget()

    w.setLayout(layout)
    w.show()

    sys.exit(app.exec_())

在函数exit_pool 中,我等到线程完成并删除 QThreadPool 实例...

有没有办法不等待每个线程直接停止一切?

编辑解决方案:

所以我以不同的方式处理了这个主题。我用QThread 替换了我的QRunnable。我删除了QThreadPool,我自己在一个列表中管理线程。我还添加了一个pyqtSignal 以停止QTimer 并通过quit() 函数关闭正在运行的线程。

就像我所有的线程都没有问题地退出了。

import sys
from PyQt5.Qt import QThread, QApplication, QWidget, QVBoxLayout
from PyQt5.Qt import QTimer, QObject, QPushButton, QLabel, pyqtSignal


class BackendQThread(QThread):
    """
        Class who create a QThread to trigger requests
    """

    quit_thread = pyqtSignal(name='close_thread')

    def __init__(self, task):
        super(BackendQThread, self).__init__()
        self.task = task

    def run(self):
        """
        Run the actions depending on the selected task

        """

        # Here I make long requests
        if 'user' in self.task:
            self.query_user_data()
        elif 'host' in self.task:
            self.query_hosts_data()
        elif 'service' in self.task:
            self.query_services_data()
        elif 'alignakdaemon' in self.task:
            self.query_daemons_data()
        elif 'livesynthesis' in self.task:
            self.query_livesynthesis_data()
        elif 'history' in self.task:
            self.query_history_data()
        elif 'notifications' in self.task:
            self.query_notifications_data()
        else:
            pass

    @staticmethod
    def query_user_data():
        """
        Launch request for "user" endpoint

        """

        print('Query user data')

    @staticmethod
    def query_hosts_data():
        """
        Launch request for "host" endpoint

        """

        print('Query hosts')

    @staticmethod
    def query_services_data():
        """
        Launch request for "service" endpoint

        """

        print("Query services")

    @staticmethod
    def query_daemons_data():
        """
        Launch request for "alignakdaemon" endpoint

        """

        print('Query daemons')

    @staticmethod
    def query_livesynthesis_data():
        """
        Launch request for "livesynthesis" endpoint

        """

        print('query livesynthesis')

    @staticmethod
    def query_history_data():
        """
        Launch request for "history" endpoint but only for hosts in "data_manager"

        """

        print('Query history')

    @staticmethod
    def query_notifications_data():
        """
        Launch request for "history" endpoint but only for notifications of current user

        """

        print('Query notifications')


class ThreadManager(QObject):
    """
        Class who create BackendQThread to periodically request on a Backend
    """

    def __init__(self, parent=None):
        super(ThreadManager, self).__init__(parent)
        self.tasks = self.get_tasks()
        self.timer = QTimer()
        self.threads = []

    def start(self):
        """
        Start ThreadManager

        """

        print("Start backend Manager...")

        # Make a first request
        self.create_tasks()

        # Then request periodically
        self.timer.setInterval(10000)
        self.timer.start()
        self.timer.timeout.connect(self.create_tasks)

    @staticmethod
    def get_tasks():
        """
        Return the available tasks to run

        :return: tasks to run
        :rtype: list
        """

        return [
            'notifications', 'livesynthesis', 'alignakdaemon', 'history', 'service', 'host', 'user',
        ]

    def create_tasks(self):
        """
        Create tasks to run

        """

        # Here I reset the list of threads
        self.threads = []
        for cur_task in self.tasks:
            backend_thread = BackendQThread(cur_task)

            # Add task to QThreadPool
            backend_thread.start()
            self.threads.append(backend_thread)

    def stop(self):
        """
        Stop the manager and close all QThreads

        """

        print("Stop tasks")
        self.timer.stop()
        for task in self.threads:
            task.quit_thread.emit()

        print("Tasks finished")


if __name__ == '__main__':
    app = QApplication(sys.argv)

    layout = QVBoxLayout()
    widget = QWidget()
    widget.setLayout(layout)

    thread_manager = ThreadManager()

    start_btn = QPushButton("Start")
    start_btn.clicked.connect(thread_manager.start)
    layout.addWidget(start_btn)

    stop_btn = QPushButton("Stop")
    stop_btn.clicked.connect(thread_manager.stop)
    layout.addWidget(stop_btn)

    widget.show()

    sys.exit(app.exec_())

【问题讨论】:

    标签: qt pyqt pyqt5 qthread


    【解决方案1】:

    QRunnable 一旦启动就无法停止。但是,您可以做一些简单的事情来减少示例中的等待时间。

    首先,您可以停止计时器,使其不再添加任何任务。其次,您可以clear the thread-pool 删除任何待处理的任务。第三,您可以尝试setting a smaller maximum thread count 看看它是否仍能达到可接受的性能。默认情况下,线程池将使用QThread.idealThreadCount() 设置最大线程数——这通常意味着系统上的每个处理器核心一个。

    最后一个选项是提供一种方法来中断在您的可运行文件中执行的代码。这只有在代码运行一个可以定期检查标志以查看它是否应该继续的循环时才真正可能。在您的示例中,您似乎可以为标志使用单个共享类属性,因为所有任务都调用静态方法。但是如果代码不能以这种方式被中断,那么您就无能为力了——您只需要等待当前正在运行的任务完成即可。

    【讨论】:

    • 好的 @ekhumoro 我已经发布了我的解决方案来管理 Qthread 并毫无问题地停止它们。感谢指出无法停止 QRunnable。
    • 您绝对可以停止已启动的QRunnable。 正如此解决方案本身所指出的,只需“...定期检查标志以查看它是否应该继续。”这是针对优雅地停止线程问题的所有框架、语言和平台的规范解决方案。所有其他解决方案都会导致数据丢失或损坏。此外,这个解决方案不需要需要迭代——尽管迭代确实可以简化问题。非迭代程序线程仍然可以通过定期检查标志并按照所述标志的指示立即返回来优雅地停止。
    • @CecilCurry。说可以停止 QRunnable 对象是荒谬的——毕竟,没有这样的公共 API 可以这样做。人们经常犯这个概念错误。他们寻找强制终止QThread 或QRunnable 的方法,而他们真正需要做的是停止正在运行的代码。他们似乎没有意识到代码阻塞了与工作线程的交互,其方式与使用 gui 的方式完全相同。我认为 Qt 是造成这种混乱的部分原因:QThread 类应该被称为 QThreadManager。
    猜你喜欢
    • 2014-01-17
    • 1970-01-01
    • 1970-01-01
    • 2020-03-25
    • 2016-02-17
    相关资源
    最近更新 更多