環境
Python 3.x
RaspberryPi 3 B
PyQt5
質問事項
まず、teamikl様に教えていただいたコードを載せさせていただきます。
(おそらく、私のやりたいことは、autoThreadInherit関数であると思いますので、その他のautoFrezz関数、autoTimer関数、autoThread関数は触れないでいきます。)
python
1from PyQt5.QtCore import Qt, QObject, QTimer, QThread, pyqtSignal, pyqtSlot 2from PyQt5.QtWidgets import ( 3 QApplication, QWidget, 4 QPushButton, QLabel, QTextBrowser, QComboBox, 5 QHBoxLayout, QVBoxLayout) 6 7 8class MainWindow(QWidget): 9 def __init__(self, parent=None): 10 super().__init__(parent) 11 self.initUI() 12 13 def initUI(self): 14 label = self.label = QLabel("") 15 button1 = self.start_button = QPushButton("Start") 16 button2 = self.stop_button = QPushButton("Stop") 17 combo = QComboBox() 18 textarea = QTextBrowser() 19 20 # 左上コンボボックスで「Start」ボタンを押した時に呼び出す関数を切り替えます。 21 items = ["autoFreeze", "autoTimer", "autoThread", "autoThreadInherit"] 22 func = None 23 def set_start_func(label): 24 nonlocal func 25 if func: 26 self.start_button.clicked.disconnect(func) 27 func = getattr(self, label) 28 self.start_button.clicked.connect(func) 29 30 import inspect 31 src = inspect.getsource(func) 32 textarea.setText(src) 33 34 combo.currentIndexChanged[str].connect(set_start_func) 35 combo.addItems(items) 36 37 vbox = QVBoxLayout() 38 vbox.addWidget(combo) 39 vbox.addWidget(label) 40 vbox.addWidget(button1) 41 vbox.addWidget(button2) 42 43 hbox = QHBoxLayout(self) 44 hbox.addLayout(vbox) 45 hbox.addWidget(textarea) 46 47 def setCount(self, count): 48 # 関数が呼ばれていることは確認 49 print(count) 50 # ラベルは更新されない -> イベントループが止まっていて描画更新がない 51 self.label.setText("Count: {}".format(count)) 52 # ラベルのプロパティ更新は確認 53 print(self.label.text()) 54 55 def autoFreeze(self): 56 """ 57 GUI がフリーズしてしまうコード 58 59 原因: メインスレッドでの永久ループ => イベントループが処理されなくなる 60 """ 61 import time 62 count = 0 63 while True: 64 count += 1 65 self.setCount(count) 66 time.sleep(1) 67 68 ## XXX 69 # 以下のコードを有効にし、自分でイベントループの処理を呼ぶと、 70 # 一時的に描画が更新され、ラベルの更新は確認できます 71 # ただし、GUI は殆どフリーズしたままです(while time.sleepにより阻害されている状態) 72 73 # QApplication.instance().processEvents() 74 75 if count >= 30: # テスト用の為、30秒で解除 76 break 77 78 79 def autoTimer(self): 80 """ 81 QTimerを使った改善方法 82 83 ※ タイマーはコードの都合上メソッド内に定義してしまいましたが、 84 実際は普通に関数として定義で良いです。 85 """ 86 count = 0 87 def countUp(): 88 nonlocal count # 外側のスコープの変数を使う宣言 89 count += 1 90 self.setCount(count) 91 92 timer = QTimer(self) 93 timer.timeout.connect(countUp) 94 timer.start(1000) 95 96 self.stop_button.clicked.connect(timer.stop) 97 98 99 def autoThread(self): 100 """ 101 QThreadを使う場合 (推奨される使い方) 102 103 この簡単な例では、全然スレッドを使う恩恵はありあせんが、 104 使い方のサンプルとして書いてみます。 105 """ 106 class MyWorker(QObject): 107 108 sendCount = pyqtSignal(int) 109 finished = pyqtSignal() 110 111 def __init__(self): 112 super().__init__() 113 self.timer = None 114 115 @pyqtSlot() 116 def stopTimer(self): 117 if self.timer: 118 self.timer.stop() 119 self.finished.emit() 120 121 @pyqtSlot() 122 def startCountUp(self): 123 # 注意点: ここで while 1: とするとスレッドのイベントループが止まります。 124 125 count = 0 126 def countUp(): 127 nonlocal count 128 count += 1 129 self.sendCount.emit(count) 130 131 timer = self.timer = QTimer(self) 132 timer.timeout.connect(countUp) 133 timer.start(1000) 134 135 thread = self.thread = QThread() 136 worker = self.worker = MyWorker() 137 worker.moveToThread(thread) 138 worker.sendCount.connect(self.setCount) # <-- スレッドから値を受け取る 139 thread.started.connect(worker.startCountUp) # <-- スレッド開始時に呼び出す 140 thread.finished.connect(worker.deleteLater) 141 142 # ワーカーのタイマーを止める場合 143 # worker.finished.connect(thread.quit) 144 # self.stop_button.clicked.connect(worker.stopTimer) 145 146 # スレッドを直接止める場合 147 self.stop_button.clicked.connect(thread.quit) 148 149 # 終了時にエラーを出さない為の後始末 150 self.destroyed.connect(worker.deleteLater) 151 self.destroyed.connect(thread.quit) 152 153 # 終了時にストップボタンのスロット解除 154 @thread.finished.connect 155 def tear_down(): 156 self.stop_button.clicked.disconnect(thread.quit) 157 158 # スレッド開始 159 thread.start() 160 161 def autoThreadInherit(self): 162 """ 163 動作するが、推奨でない使い方 164 165 run()をオーバーライドする使い方もドキュメントに記載はあります。 166 167 完全に間違った使い方というわけではありませんが、 168 何故、別の方法が推奨されているのか把握していないと、 169 意図しない動作に悩まさせられることがあります。 170 """ 171 import time 172 # XXX: QThreadのサンプルコードを参考にするとき、継承して使ってるコードには注意 173 class MyThread(QThread): 174 sendCount = pyqtSignal(int) 175 176 # XXX: run()を上書きすると、スレッドのイベントループ起動を上書きします 177 # スレッドのイベントループは起動されません。一応、スレッドの実行自体は問題ありません。 178 def run(self): 179 # XXX: 何故この方法が推奨されないのか 180 # --> QTimer 等がスレッド上で使えない。 181 182 # run()を終了するとスレッドも終了なので 183 # スレッドを維持する場合、ループが必要になります => Pythonのスレッドで良い 184 count = 0 185 while 1: # XXX: <- infinity loop は abuse of thread 186 count += 1 187 self.sendCount.emit(count) 188 self.sleep(1) 189 190 # XXX: このループは強制終了以外に終了手段がないため 191 # スレッド終了時のfinished シグナルは送られません 192 193 thread = self.thread = MyThread() 194 thread.moveToThread(thread) # WRONG! 195 thread.sendCount.connect(self.setCount) 196 197 # XXX: スレッドのイベントループが動いてない為、処理されない例 198 # self.stop_button.clicked.connect(thread.terminate) 199 200 # 直接呼び出しは可能(安全ではないかもしれません) 201 terminate = lambda: thread.terminate() 202 self.stop_button.clicked.connect(terminate) 203 204 # 終了時にストップボタンのスロット解除 205 @thread.finished.connect 206 def tear_down(): 207 self.stop_button.clicked.disconnect(terminate) 208 209 thread.start() 210 211 212def main(): 213 import sys 214 app = QApplication(sys.argv) 215 win = MainWindow() 216 win.resize(800, 600) 217 win.setWindowTitle("CountUp Demo (QTimer/QThread)") 218 win.show() 219 sys.exit(app.exec_()) 220 221 222if __name__ == '__main__': 223 main() 224
分からないこと
①line(22~28)
func=getattr(self,label) は func=self.label と同じ意味であると勉強しました。ここでのlabelは、GUI上にある空欄のテキストボックス?であると解釈しています。
そこで、if func: とは何を表すのでしょうか?
また、 self.start_button.clicked.disconnect(func) と self.start_button.clicked.connect(func) はどういう意味なのでしょうか?(ボタンを押すとfuncにconnect,disconnectするのは理解しているのですが、funcがどのように動作しているものなのか分からないので、結論何が何だか分からない。となってしまいます。)
②line(161~209)
ここにある while 1: (line 185) の説明に「このループは強制終了しか終了手段がない」とありますが、強制終了というのは、メインスレッドからの run()=Folse による run() の上書きでできるのでしょうか?
line(193~195)について
thread=self.thread=MyThread() →サブスレッドの作成を意味している?
thread.moveToThread(thread) →作成したサブスレッドへ移動している?
thread.sendCount.connect(self.setCount) →サブスレッドからメインスレッドのsetCount関数にデータを送れるようにしている?
line(201,202)について
terminate = lambda: thread.terminate() は
def terminate():
ruturn thread.terminate()
と同じであると勉強しました。 このとき、line 202 の実行でサブスレッドを終了させているのでしょうか?
line(205~207)について
そもそもline(205)は何を表しているのでしょうか?またline(206,207)はどういう動作をしているのでしょうか?
とりあえず、今現在、これらについて分かりません。
分からないことが多すぎて大変申し訳ありませんが、教えてください。
よろしくお願いします。
回答5件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。