首页 > 解决方案 > 单击按钮时如何停止线程(QRunnable)?

问题描述

我阅读了这篇关于 PyQt 中的线程的文章,并尝试将其实现到我自己的程序中。我现在可以启动一个线程,但是当我关闭窗口时,我认为另一个线程会继续运行。我也不能使用 ctrl+c 来中断程序。那么如何停止发送通知的我的线程呢?这是我的代码:

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QRunnable, pyqtSlot, QThreadPool
import notify2
import time


class StartTimer(QRunnable):
    '''
    StartTimer thread
    '''

    def __init__(self, minutes):
        super(StartTimer, self).__init__()
        self.minutes = minutes

    @pyqtSlot()
    def run(self):
        notify2.init('Break Timer')
        notification = notify2.Notification(
            'Success!', 'Break Timer has been started.')
        notification.show()

        self.start = True

        while self.start:
            time.sleep(self.minutes * 60)

            notify2.init('Break Timer')
            notification = notify2.Notification(
                'Pause for 1 minute', 'Move and see away from the screen!')
            notification.show()

        time.sleep(5)


class Ui_break_timer_ui(object):

    def __init__(self):
        self.threadpool = QThreadPool()
        self.start = False

    def start_btn_pressed(self):
        self.start_timer = StartTimer(self.spinBox_minutes.value())
        self.threadpool.start(self.start_timer)

    def stop_timer(self):
        notify2.init('Break Timer')
        notification = notify2.Notification(
            'Program ended', 'You will receive no further notifications.')
        notification.show()

        self.start = False

    def setupUi(self, break_timer_ui):
        break_timer_ui.setObjectName("break_timer_ui")
        break_timer_ui.setWindowIcon(QtGui.QIcon('icon.png'))
        break_timer_ui.resize(596, 412)
        self.centralwidget = QtWidgets.QWidget(break_timer_ui)
        self.centralwidget.setObjectName("centralwidget")
        self.btn_start = QtWidgets.QPushButton(self.centralwidget)
        self.btn_start.setGeometry(QtCore.QRect(490, 350, 88, 34))
        self.btn_start.setObjectName("btn_start")
        self.btn_stop = QtWidgets.QPushButton(self.centralwidget)
        self.btn_stop.setGeometry(QtCore.QRect(390, 350, 88, 34))
        self.btn_stop.setObjectName("btn_stop")
        self.label_set_interval = QtWidgets.QLabel(self.centralwidget)
        self.label_set_interval.setGeometry(QtCore.QRect(20, 20, 91, 18))
        self.label_set_interval.setObjectName("label_set_interval")
        self.spinBox_minutes = QtWidgets.QSpinBox(self.centralwidget)
        self.spinBox_minutes.setGeometry(QtCore.QRect(20, 50, 52, 32))
        self.spinBox_minutes.setMinimum(1)
        self.spinBox_minutes.setMaximum(240)
        self.spinBox_minutes.setProperty("value", 30)
        self.spinBox_minutes.setObjectName("spinBox_minutes")
        self.label_minutes = QtWidgets.QLabel(self.centralwidget)
        self.label_minutes.setGeometry(QtCore.QRect(80, 60, 58, 18))
        self.label_minutes.setObjectName("label_minutes")
        break_timer_ui.setCentralWidget(self.centralwidget)
        self.statusbar = QtWidgets.QStatusBar(break_timer_ui)
        self.statusbar.setObjectName("statusbar")
        break_timer_ui.setStatusBar(self.statusbar)

        self.btn_stop.clicked.connect(lambda _: self.stop_timer())

        self.btn_start.clicked.connect(lambda _: self.start_btn_pressed())

        self.retranslateUi(break_timer_ui)
        QtCore.QMetaObject.connectSlotsByName(break_timer_ui)

    def retranslateUi(self, break_timer_ui):
        _translate = QtCore.QCoreApplication.translate
        break_timer_ui.setWindowTitle(
            _translate("break_timer_ui", "Break Timer"))
        self.btn_start.setText(_translate("break_timer_ui", "Start"))
        self.btn_stop.setText(_translate("break_timer_ui", "Stop"))
        self.label_set_interval.setText(
            _translate("break_timer_ui", "Set interval"))
        self.label_minutes.setText(_translate("break_timer_ui", "minutes"))


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    break_timer_ui = QtWidgets.QMainWindow()
    ui = Ui_break_timer_ui()
    ui.setupUi(break_timer_ui)
    break_timer_ui.show()
    sys.exit(app.exec_())

标签: pythonmultithreadingpyqtpyqt5qrunnable

解决方案


QRunnable 更适用于运行并完成的代码(即使它是长时间运行的),而不是持久的后台线程。

你想做的事情很少。首先,查看https://doc.qt.io/qt-5/qthread.html。你想要做的是让你的后台任务成为一个 QObject,给它一个 run() 槽,然后将 thread.started 连接到 worker.run。这就是启动该过程的原因。您需要做丑陋的两部分方式,而不仅仅是子类 QThread,因为您希望线程有自己的事件循环。

你仍然有一个问题,你有一个长时间的睡眠,并且睡眠调用阻塞了整个线程,所以你不能在几分钟内取消它。让您的 worker.run() 创建一个 QTimer(因此它位于线程事件循环中,而不是主 GUI 事件循环中),并使用该 QTimer 向您的工作人员上的另一个插槽发出信号,它是时候唤醒了。现在没有长时间的 sleep() 阻塞,只有事件。这意味着当您调用 thread.quit() 时,它将发出完成(您可以连接到您的工作人员以进行任何关闭操作)然后停止线程,无论计时器上还剩多少时间。

综上所述,如果您在这里尝试做的只是您在这里所做的,您可以在主线程中使用 QTimer 并完全避免整个多线程操作,但我认为这只是一些更宏大方案的演示.


推荐阅读