单击按钮时如何停止线程(QRunnable)?

How do I stop a thread (QRunnable) when clicking on a button?

我阅读了 this 关于 PyQt 中线程的文章,并尝试将其实现到我自己的程序中。我现在可以启动一个线程,但是当我关闭 window 时,我认为另一个线程保持 运行。我也不能用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_())

QRunnable 更适用于 运行-完成代码(即使它很长 运行ning)而不是持久后台线程。

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

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

综上所述,如果你在这里尝试做的就是你在这里做的,你可以在主线程中使用 QTimer 并完全避免整个多线程的事情,但我认为这只是一个一些更宏伟计划的演示。