Issue
I read this article about Threading in PyQt and tried to implement it into my own program. I am now able to start a thread, but when I close the window I think the other thread keeps running. I also can't use ctrl+c to interrupt the program. So how do I stop this my thread, that sends the notifications? Here's my code:
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_())
Solution
QRunnable is more for run-and-done code (even if it's long running) than it is for a persistent background thread.
Few things you want to do. First, look at https://doc.qt.io/qt-5/qthread.html. What you want to do is make your background task a QObject, give it a run() slot, and connect the thread.started to the worker.run. That's what will kick the process off. You need to do the ugly two-part way, not just subclass QThread, because you want the thread to have its own event loop.
You've still got a problem where you've got a LONG sleep, and that sleep call blocks the entire thread, so you can't cancel it for minutes. Have your worker.run() create a QTimer (so it's in the thread event loop not the main GUI event loop), and use that QTimer to signal another slot on your worker that it's time to wake up. Now there's no long sleep() blocking, just events. Which means that when you call thread.quit() it will emit finished (which you can connect to your worker for any shutdown actions) and then stop the thread regardless of how much time is left on the timer.
All that said, if all you were trying to do here is what you're doing here, you could just use a QTimer in the main thread and avoid the whole multithreading thing entirely, but I assume this was just a demo of some grander scheme.
Answered By - Rgaddi
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.