#やりたいこと
サブスレッド実行中に外部キー割り込みでサブスレッドを終了したい
#問題
添付コードでサブスレッドを終了したかったのですが、終了できませんでした。
コードの流れとしては次の通りです。
1メインスレッド、サブスレッド定義
2Executeが押される
└サブスレッドの関数executeが実行
3Ctrl+Cをキーボード入力
└サブスレッド終了⇒終了しない
※Ctrl+C判定での終了は以下を参考にしました
https://kusanohitoshi.blogspot.com/2017/01/pythonctrl-c.html?m=1
python
1import sys 2import logging 3import time 4import signal 5from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot 6from PyQt5.QtWidgets import ( 7 QApplication, 8 QWidget, 9 QLineEdit, 10 QPlainTextEdit, 11 QPushButton, 12 QGridLayout, 13 ) 14 15FLAG = False 16class Worker(QObject): 17 18 done = pyqtSignal(str) 19 20 @pyqtSlot(str) 21 def execute(self, text): 22 global FLAG 23 # シグナル経由で呼び出すと別スレッドで実行されますが、 24 # シグナル&スロット機構を使う場合は、イベントループを使う必要があり、 25 # スレッドのイベントループを正常に稼働させるためには、 26 # 個々の関数(スロット)は即時に終了しなければなりません。 27 # 28 # 時間のかかる処理を行いたい場合は、 29 # 毎回新しいスレッドを生成するか、この中で更に別スレッドを使います。 30 31 while not FLAG: 32 print("Waiting for signal...") 33 print("FLAG = {}".format(FLAG)) 34 time.sleep(1) 35 36 37 38 39 40class View(QWidget): 41 42 sendMessage = pyqtSignal(str) 43 44 def __init__(self, parent=None): 45 super().__init__(parent) 46 lineEdit = QLineEdit(self) 47 button = QPushButton("Execute", self) 48 textEdit = QPlainTextEdit(self) 49 50 lineEdit.returnPressed.connect(self._onClicked) 51 button.clicked.connect(self._onClicked) 52 53 layout = QGridLayout(self) 54 layout.addWidget(lineEdit, 0, 0) 55 layout.addWidget(button, 0, 1) 56 layout.addWidget(textEdit, 1, 0, 1, 2) 57 58 self._button = button 59 self._textEdit = textEdit 60 self._lineEdit = lineEdit 61 62 def _onClicked(self): 63 text = self._lineEdit.text() 64 self.sendMessage.emit(text) 65 self._lineEdit.clear() 66 67 @pyqtSlot(str) 68 def addResultText(self, text): 69 logging.info("addResultText: %s", text) 70 self._textEdit.appendPlainText(text) 71 72def handler(signum, frame): 73 global FLAG 74 print("signal={}".format(signum)) 75 FLAG = True 76 77def main(): 78 app = QApplication(sys.argv) 79 view = View() 80 81 # スレッド開始 82 thread = QThread(app) 83 worker = Worker() 84 worker.moveToThread(thread) 85 thread.start() 86 #シグナル定義 87 signal.signal(signal.SIGINT, handler) 88 # view: メインスレッド, worker: 別スレッド 89 view.sendMessage.connect(worker.execute) 90 worker.done.connect(view.addResultText) 91 92 93 94 95 96 # プログラム終了時にエラーが出ないように、スレッドの後始末 97 # ※ QThread の使い方によって後始末の方法が異なるので注意 98 # QThread.quit は、スレッドのイベントループを終了します。 99 app.aboutToQuit.connect(thread.quit) 100 101 view.show() 102 sys.exit(app.exec_()) 103 104 105if __name__ == '__main__': 106 logging.basicConfig( 107 level=logging.DEBUG, 108 # ログにスレッド名を表示するための書式設定 109 # 110 # ※ 制限: QThread のスレッド名は Dummy-1 のように表示されますが、 111 # 今回は、メインスレッドかそうでないかを区別できれば充分です。 112 format="[%(threadName)-10s][%(levelname)-8s] %(message)s", 113 ) 114 main()
#質問
上記問題を解決方法で何か良い方法はありますでしょうか
#解決したコード
python
1import sys 2import logging 3import time 4import signal 5from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot 6from PyQt5.QtWidgets import ( 7 QApplication, 8 QWidget, 9 QLineEdit, 10 QPlainTextEdit, 11 QPushButton, 12 QGridLayout, 13 QShortcut, 14 ) 15from PyQt5.QtGui import QKeySequence 16from PyQt5 import QtGui 17FLAG = False 18class Worker(QObject): 19 20 done = pyqtSignal(str) 21 22 @pyqtSlot(str) 23 def execute(self, text): 24 global FLAG 25 FLAG = False 26 # シグナル経由で呼び出すと別スレッドで実行されますが、 27 # シグナル&スロット機構を使う場合は、イベントループを使う必要があり、 28 # スレッドのイベントループを正常に稼働させるためには、 29 # 個々の関数(スロット)は即時に終了しなければなりません。 30 # 31 # 時間のかかる処理を行いたい場合は、 32 # 毎回新しいスレッドを生成するか、この中で更に別スレッドを使います。 33 34 while not FLAG: 35 print("Waiting for signal...") 36 print("FLAG = {}".format(FLAG)) 37 time.sleep(1) 38 39 40 41 42 43class View(QWidget): 44 45 sendMessage = pyqtSignal(str) 46 47 def __init__(self, parent=None): 48 super().__init__(parent) 49 lineEdit = QLineEdit(self) 50 button = QPushButton("Execute", self) 51 textEdit = QPlainTextEdit(self) 52 53 lineEdit.returnPressed.connect(self._onClicked) 54 button.clicked.connect(self._onClicked) 55 56 layout = QGridLayout(self) 57 layout.addWidget(lineEdit, 0, 0) 58 layout.addWidget(button, 0, 1) 59 layout.addWidget(textEdit, 1, 0, 1, 2) 60 61 self._button = button 62 self._textEdit = textEdit 63 self._lineEdit = lineEdit 64 65 def _onClicked(self): 66 text = self._lineEdit.text() 67 self.sendMessage.emit(text) 68 self._lineEdit.clear() 69 70 @pyqtSlot(str) 71 def addResultText(self, text): 72 global FLAG 73 logging.info("addResultText: %s", text) 74 self._textEdit.appendPlainText(text) 75 FLAG = True 76 77def onControlC(): 78 global FLAG 79 print("Ctrl-C pressed") 80 FLAG = True 81 82def main(): 83 84 app = QApplication(sys.argv) 85 view = View() 86 87 # スレッド開始 88 thread = QThread(app) 89 worker = Worker() 90 worker.moveToThread(thread) 91 thread.start() 92 #シグナル定義 93 94 # view: メインスレッド, worker: 別スレッド 95 view.sendMessage.connect(worker.execute) 96 worker.done.connect(view.addResultText) 97 98 99 100 101 shortcut = QShortcut(QKeySequence('Ctrl+C'), view) 102 shortcut.activated.connect(onControlC) 103 104 105 # プログラム終了時にエラーが出ないように、スレッドの後始末 106 # ※ QThread の使い方によって後始末の方法が異なるので注意 107 # QThread.quit は、スレッドのイベントループを終了します。 108 app.aboutToQuit.connect(thread.quit) 109 110 view.show() 111 sys.exit(app.exec_()) 112 113 114if __name__ == '__main__': 115 logging.basicConfig( 116 level=logging.DEBUG, 117 # ログにスレッド名を表示するための書式設定 118 # 119 # ※ 制限: QThread のスレッド名は Dummy-1 のように表示されますが、 120 # 今回は、メインスレッドかそうでないかを区別できれば充分です。 121 format="[%(threadName)-10s][%(levelname)-8s] %(message)s", 122 ) 123 main()
曖昧な点の確認ですが、
質問のコードのスレッドでのフラグでループを抜ける使い方では、
「スレッド内で行ってる処理の終了」にはなりますが、
「スレッド自身の終了」にはなりません。
ループを抜けてスレッドが終了となるのは、
QThreadを継承で使った場合です。
moveToThread を使った場合は、スレッドはイベントループを実行し
その内部でスロットが呼ばれる形になるので、
ループを抜けてもスレッドは残り続けて、次のイベント処理を待ち続けます。
実行中の処理を中断したいだけなら、
スレッドを終了させる必要はないので、
コード自体はこれでいいと思いますが、動作自体は
質問されている「サブスレッドの終了」ではない点は確認してください。
回答2件
あなたの回答
tips
プレビュー