🍣
PySide6(Qt for Python) のGUIアプリで、scheduleモジュールで定期実行。(QThread,QTimer)
注意したこと
- scheduleは別スレッドで動かす。そうしないとscheduleが、gui要素関連の処理を呼び出す時、schedule.run_pending()のループが止まってしまう。
- 通常は、schedule.run_pending()をwhile文で無限ループするが、そうすると、プログラムを終了する時、Scheduler_Threadが終了できない。
- そのためにwhile文ではなくQTimerで、1秒ごとにschedule.run_pending()を呼び出す。
- しかし、別スレッドでQTimerは利用できない。↓
You must start and stop the timer in its thread.
It is not possible to start a timer from another thread.
(公式ドキュメントより)
- また、メインスレッド以外で、QMessageBoxなどGUI要素を動かすことは推奨されない。予期しない動作をする可能性がある。
- そのために、QTimerや、GUIはメインスレッドで動かし、scheduleは別スレッドで動かす。
ソースコードの例
以下は、scheduleモジュールで、定期的に QMessageBox を表示するプログラムである。
import schedule
from PySide6 import QtCore, QtWidgets, QtGui
import sys
class alert_regularly(QtCore.QTimer):
def __init__(self) -> None:
super().__init__()
self.scheduler_thread = Scheduler_Thread(self)
self.setInterval(1000)
self.timeout.connect(self.scheduler_thread.start)
self.scheduler_thread.show_alart_time.connect(self.alart)
self.start()
def alart(self,message:str):
return QtWidgets.QMessageBox.information(None, "通知", message, QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.NoButton)
# 念のため
def __del__(self) -> None:
self.scheduler_thread.stop()
class Scheduler_Thread(QtCore.QThread):
show_alart_time = QtCore.Signal(str)
def __init__(self, object : QtCore.QObject) -> None:
super().__init__(object)
self.object : QtCore.QObject = object
schedule.every(4).seconds.do(self.alart)
def alart(self) -> None:
# signalで送る
self.show_alart_time.emit("時間です")
def run(self) -> None:
schedule.run_pending()
def stop(self):
self.quit()
self.wait()
class MainWindow(QtWidgets.QWidget):
def __init__(self) -> None:
super().__init__()
self.init_widget()
self.alert = alert_regularly()
def init_widget(self) -> None:
self.setGeometry(300, 200, 400, 300)
self.setWindowTitle("Close button")
button = QtWidgets.QPushButton("close", self)
button.move(150, 100)
button.clicked.connect(self.close)
def main():
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()
if __name__ == "__main__" :
main()
QThreadではなく、threadingを利用しても良いが、Signalなどを使えないため、カスタムイベントを作る必要がある。
スレッドをデーモンとすることで、プログラム終了時、スレッドも一緒に終了させる事ができるため、QTimerは必要ない。
main.py
import schedule
import threading
from PySide6 import QtCore, QtWidgets, QtGui
import sys
import time
class QEvent_Custom_ShowMessageBox(QtCore.QEvent):
# QMessageBox を表示するためのカスタムイベント
custom_type = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
def __init__(self) -> None:
super().__init__(self.custom_type)
class MainWindow(QtWidgets.QWidget):
# 主ウィンジット
def __init__(self) -> None:
super().__init__()
self.init_widget()
self.scheduler_thread = Scheduler_Thread(self)
def init_widget(self) -> None:
self.setGeometry(500, 300, 400, 270)
self.setWindowTitle("test")
button = QtWidgets.QPushButton("close", self)
button.move(150, 70)
button.clicked.connect(self.close)
def event(self, event: QtCore.QEvent) -> bool:
# カスタムイベントの処理
if isinstance(event,QEvent_Custom_ShowMessageBox):
QtWidgets.QMessageBox.information(None, "通知", "インフォメーションです。", QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.NoButton)
return True
return super().event(event)
class Scheduler_Thread(threading.Thread):
# 定期的に QMessageBox を表示するためのスレッド
def __init__(self, main_window: MainWindow) -> None:
super().__init__(daemon=True)
self.main_window = main_window
# schedule.every().day.at("23:00").do(self.alart)
schedule.every(4).seconds.do(self.alart)
self.start()
def alart(self) -> None:
# QMessageBox を表示するカスタムイベントをポストする
QtCore.QCoreApplication.postEvent(self.main_window, QEvent_Custom_ShowMessageBox())
def run(self) -> None:
while True:
schedule.run_pending()
time.sleep(1)
def main():
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()
if __name__ == "__main__" :
main()
参考までに。
Discussion