環境
Python 3.x
RaspberryPi 3 B
PyQt5
質問事項
まず、teamikl様に教えていただいたコードを載せさせていただきます。
(おそらく、私のやりたいことは、autoThreadInherit関数であると思いますので、その他のautoFrezz関数、autoTimer関数、autoThread関数は触れないでいきます。)
python
from PyQt5.QtCore import Qt, QObject, QTimer, QThread, pyqtSignal, pyqtSlot from PyQt5.QtWidgets import ( QApplication, QWidget, QPushButton, QLabel, QTextBrowser, QComboBox, QHBoxLayout, QVBoxLayout) class MainWindow(QWidget): def __init__(self, parent=None): super().__init__(parent) self.initUI() def initUI(self): label = self.label = QLabel("") button1 = self.start_button = QPushButton("Start") button2 = self.stop_button = QPushButton("Stop") combo = QComboBox() textarea = QTextBrowser() # 左上コンボボックスで「Start」ボタンを押した時に呼び出す関数を切り替えます。 items = ["autoFreeze", "autoTimer", "autoThread", "autoThreadInherit"] func = None def set_start_func(label): nonlocal func if func: self.start_button.clicked.disconnect(func) func = getattr(self, label) self.start_button.clicked.connect(func) import inspect src = inspect.getsource(func) textarea.setText(src) combo.currentIndexChanged[str].connect(set_start_func) combo.addItems(items) vbox = QVBoxLayout() vbox.addWidget(combo) vbox.addWidget(label) vbox.addWidget(button1) vbox.addWidget(button2) hbox = QHBoxLayout(self) hbox.addLayout(vbox) hbox.addWidget(textarea) def setCount(self, count): # 関数が呼ばれていることは確認 print(count) # ラベルは更新されない -> イベントループが止まっていて描画更新がない self.label.setText("Count: {}".format(count)) # ラベルのプロパティ更新は確認 print(self.label.text()) def autoFreeze(self): """ GUI がフリーズしてしまうコード 原因: メインスレッドでの永久ループ => イベントループが処理されなくなる """ import time count = 0 while True: count += 1 self.setCount(count) time.sleep(1) ## XXX # 以下のコードを有効にし、自分でイベントループの処理を呼ぶと、 # 一時的に描画が更新され、ラベルの更新は確認できます # ただし、GUI は殆どフリーズしたままです(while time.sleepにより阻害されている状態) # QApplication.instance().processEvents() if count >= 30: # テスト用の為、30秒で解除 break def autoTimer(self): """ QTimerを使った改善方法 ※ タイマーはコードの都合上メソッド内に定義してしまいましたが、 実際は普通に関数として定義で良いです。 """ count = 0 def countUp(): nonlocal count # 外側のスコープの変数を使う宣言 count += 1 self.setCount(count) timer = QTimer(self) timer.timeout.connect(countUp) timer.start(1000) self.stop_button.clicked.connect(timer.stop) def autoThread(self): """ QThreadを使う場合 (推奨される使い方) この簡単な例では、全然スレッドを使う恩恵はありあせんが、 使い方のサンプルとして書いてみます。 """ class MyWorker(QObject): sendCount = pyqtSignal(int) finished = pyqtSignal() def __init__(self): super().__init__() self.timer = None @pyqtSlot() def stopTimer(self): if self.timer: self.timer.stop() self.finished.emit() @pyqtSlot() def startCountUp(self): # 注意点: ここで while 1: とするとスレッドのイベントループが止まります。 count = 0 def countUp(): nonlocal count count += 1 self.sendCount.emit(count) timer = self.timer = QTimer(self) timer.timeout.connect(countUp) timer.start(1000) thread = self.thread = QThread() worker = self.worker = MyWorker() worker.moveToThread(thread) worker.sendCount.connect(self.setCount) # <-- スレッドから値を受け取る thread.started.connect(worker.startCountUp) # <-- スレッド開始時に呼び出す thread.finished.connect(worker.deleteLater) # ワーカーのタイマーを止める場合 # worker.finished.connect(thread.quit) # self.stop_button.clicked.connect(worker.stopTimer) # スレッドを直接止める場合 self.stop_button.clicked.connect(thread.quit) # 終了時にエラーを出さない為の後始末 self.destroyed.connect(worker.deleteLater) self.destroyed.connect(thread.quit) # 終了時にストップボタンのスロット解除 @thread.finished.connect def tear_down(): self.stop_button.clicked.disconnect(thread.quit) # スレッド開始 thread.start() def autoThreadInherit(self): """ 動作するが、推奨でない使い方 run()をオーバーライドする使い方もドキュメントに記載はあります。 完全に間違った使い方というわけではありませんが、 何故、別の方法が推奨されているのか把握していないと、 意図しない動作に悩まさせられることがあります。 """ import time # XXX: QThreadのサンプルコードを参考にするとき、継承して使ってるコードには注意 class MyThread(QThread): sendCount = pyqtSignal(int) # XXX: run()を上書きすると、スレッドのイベントループ起動を上書きします # スレッドのイベントループは起動されません。一応、スレッドの実行自体は問題ありません。 def run(self): # XXX: 何故この方法が推奨されないのか # --> QTimer 等がスレッド上で使えない。 # run()を終了するとスレッドも終了なので # スレッドを維持する場合、ループが必要になります => Pythonのスレッドで良い count = 0 while 1: # XXX: <- infinity loop は abuse of thread count += 1 self.sendCount.emit(count) self.sleep(1) # XXX: このループは強制終了以外に終了手段がないため # スレッド終了時のfinished シグナルは送られません thread = self.thread = MyThread() thread.moveToThread(thread) # WRONG! thread.sendCount.connect(self.setCount) # XXX: スレッドのイベントループが動いてない為、処理されない例 # self.stop_button.clicked.connect(thread.terminate) # 直接呼び出しは可能(安全ではないかもしれません) terminate = lambda: thread.terminate() self.stop_button.clicked.connect(terminate) # 終了時にストップボタンのスロット解除 @thread.finished.connect def tear_down(): self.stop_button.clicked.disconnect(terminate) thread.start() def main(): import sys app = QApplication(sys.argv) win = MainWindow() win.resize(800, 600) win.setWindowTitle("CountUp Demo (QTimer/QThread)") win.show() sys.exit(app.exec_()) if __name__ == '__main__': main()
分からないこと
①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)はどういう動作をしているのでしょうか?
とりあえず、今現在、これらについて分かりません。
分からないことが多すぎて大変申し訳ありませんが、教えてください。
よろしくお願いします。
まだ回答がついていません
会員登録して回答してみよう