🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Raspberry Pi

Raspberry Piは、ラズベリーパイ財団が開発した、名刺サイズのLinuxコンピュータです。 学校で基本的なコンピュータ科学の教育を促進することを意図しています。

Q&A

解決済

5回答

3122閲覧

QThreadのコードを教えてください

pythonnoob1

総合スコア18

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Raspberry Pi

Raspberry Piは、ラズベリーパイ財団が開発した、名刺サイズのLinuxコンピュータです。 学校で基本的なコンピュータ科学の教育を促進することを意図しています。

0グッド

2クリップ

投稿2020/06/04 11:27

環境

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)はどういう動作をしているのでしょうか?

とりあえず、今現在、これらについて分かりません。
分からないことが多すぎて大変申し訳ありませんが、教えてください。
よろしくお願いします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答5

0

markdownを使う為、回答での返信になります。>コメント元

QThread を継承して QThreadのイベントループは用いない方法ですね。
まずはその方針ですすめましょう。


総括すると、「イベントループ」の存在が理解のカギなのかなというような印象を受けました。

(シグナル&スロットやタイマーを使うために)イベントループを活用する場合
ループの中でループを組むことになるので、外側のループとどのように折り合いをつけるのかが課題です。

例えば、タイマーのイベント内での時間のかかるループ処理をする場合 (QThread + worker の場合も同様)

while 1: # イベントループ      シグナル&スロットの処理 (タイマーのイベント等)     while 1: # 自分で組んだループ ... 実行中は外側のループの処理が停滞する       GPIOの入力監視・待機等   (メインスレッドであれば追加でGUIの描画更新等 ... これが呼ばれないと画面が固まる)

要点: 自分で書いた関数が何処で呼ばれているかを把握する。

一般的にGUIでのよくある質問では、外側のイベントループを意識されてないケースが多く、
以下は、何れもイベントループが停滞することによる弊害です

  • GUI がフリーズする (メインスレッドの場合)
  • シグナル&スロットが処理されない
  • タイマーが意図通りのタイミングにならない

QThreadを継承する方法では

while 1: # 自分で組むループが一番外側になる → 外部への影響を気にする必要がない   GPIOの入力監視・待機等 # ループを抜けるとスレッド終了
  • イベントループを使わない場合、スレッド側のシグナル&スロット機構が使えない。
  • ループの中断を自分で実装する必要あり

きちんとデメリットを把握しての利用なら問題ないと思います。

QThreadの中断については、他の回答にあるサンプルコードを更新します。
自分で中断処理を実装する必要があるのは変わりませんが、QThreadの提供するフラグが使えます。


ちなみに、私は GPIO 周りは全く解らないのですが、
スレッドや GUIの関連、非同期IO ならお役に立てると思います。

以前他の質問で GPIO のライブラリを少し調べた程度。実際に動かしたことはありません。

 discordでの利用例ですが、GPIOの同期スレッドと「非同期IO」で連携する方法。

投稿2020/06/07 06:37

teamikl

総合スコア8742

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

タイマーの動作確認用コード

(ブラウザ上で実行して動作確認できます)
https://repl.it/@MiKLTea/QTimer-and-EventLoop-demo

時間のかかる処理中にイベントループを動かす方法を追記。(コメント内)

設計から手を入れられる段階では、この方法は避けた方が良いと思いますが、
イベントループが行うはずだった処理を自分で組んだループ内で定期的に呼び出す事で、
イベントループの停止を回避できます。→ processEvents

python

1 2def main(): 3 import sys 4 from PyQt5.QtCore import QCoreApplication, QTimer 5 6 app = QCoreApplication(sys.argv) 7 timer = QTimer(app) 8 count = 0 9 10 def ping(): # タイマーの動作を確認する為に毎秒何か表示 11 nonlocal count 12 count += 1 13 print("PING", count) 14 15 timer.timeout.connect(ping) 16 timer.start(1*1000) 17 18 19 def heavy_task(): 20 import time 21 print("HEAVY TASK START") 22 # 10秒間待つ 23 for _ in range(100): 24 time.sleep(0.1) 25 26 # イベントループを動かす 27 # app.processEvents() 28 29 print("HEAVY TASK DONE") 30 31 ## XXX: 3秒後に時間のかかる処理を実行 32 # 33 # 以下のコメントを外して動作を確認して見て下さい 34 QTimer.singleShot(3*1000, heavy_task) 35 36 # 20秒後に終了 37 QTimer.singleShot(20*1000, app.quit) 38 39 sys.exit(app.exec_()) 40 41if __name__ == '__main__': 42 main()

投稿2020/06/05 12:01

編集2020/06/05 13:41
teamikl

総合スコア8742

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

こちらの質問への回答で良いでしょうか。
前回の質問からの流れもある点をご了承ください。


QThread と Python のスレッドでのループのコードです。

Pythonのスレッドももう一つ関数を指定して実行する方法もありますが、割愛して
同じスレッドクラスを継承し run() をオーバーライドする方法で実装しました。

ループを抜けるフラグは、少し手抜で外側の変数を直接参照してますが、
その辺りは改良の余地ありで、出来ればスレッドへの引数で渡すようにします。

Pythonのスレッドを使う方は、少し注意が必要で
スレッド内でGUIへアクセスしていた場合は、終了処理を適切に行わないと(-> closeEventを参照)
Qtのメインループ後にGUIへのアクセスが発生するとエラーになります。

QThreadの方が、オブジェクトの管理をQtの仕組みに任せられるのと、
片方向ですがシグナル&スロットを使える分利点が多そうでした。

** [2020-06-07] 追記:** repl.it 上にコードを載せました。
ソースコメント内の日本語は文字化けしますが、ブラウザ上で実行可能です。

ロギングでどのスレッドで実行されてるかを解るようにしたので、
setCountがどのスレッドから実行されてるか辺りを着目してください

  • Python スレッド ... サブスレッドで実行(スレッドセーフでない)
  • QThread スレッド ... メインスレッドで実行 (スレッドセーフ)

前者の場合は、より安全にするなら追加でロック機構
複数のスレッドから同時にアクセスさせないようにする仕組みの導入が必要です。
(このサンプルでは問題ありませんが、場合によってはプログラムがクラッシュしたりします)

QThread を使う方法では、
メインスレッドのイベントループが順番に処理する為、
同時アクセスは発生せずロックは不要です。(シグナル&スロットとイベントループを利用する恩恵)

[source on repl.it]

python

1#!/usr/bin/env python3.8 2 3import logging 4from threading import Thread, current_thread 5 6from PyQt5.QtCore import Qt, QObject, QTimer, QThread, pyqtSignal, pyqtSlot 7from PyQt5.QtWidgets import ( 8 QApplication, QWidget, 9 QPushButton, QLabel, QTextBrowser, QComboBox, 10 QHBoxLayout, QVBoxLayout) 11 12logger = logging.getLogger(__name__) 13 14 15class MainWindow(QWidget): 16 def __init__(self, parent=None): 17 super().__init__(parent) 18 self.py_thread = None 19 self.qt_thread = None 20 self.running = False 21 self.initUI() 22 23 def closeEvent(self, event): 24 self.cancelThreadLoop() 25 26 # Pythonスレッドが起動していれば、終了を待つ 27 if self.py_thread: 28 self.py_thread.join() 29 30 if self.qt_thread: 31 self.qt_thread.wait(2000) # sleep の 1秒以上待つ 32 33 super().closeEvent(event) 34 35 def cancelThreadLoop(self): 36 logger.debug("cancel") 37 38 # autoThreadPython, autoThreadQt の中断。 39 # TODO: このコードは Thread-Safe でないので、Mutexを使った方が良い。 40 self.running = False 41 42 if self.qt_thread: 43 # autoInterruptionThreadQt の中断 44 self.qt_thread.requestInterruption() 45 46 def initUI(self): 47 label = self.label = QLabel("") 48 button1 = self.start_button = QPushButton("Start") 49 button2 = self.stop_button = QPushButton("Stop") 50 combo = QComboBox() 51 textarea = QTextBrowser() 52 53 items = ["autoThreadPython", "autoThreadQt", "autoInterruptionThreadQt"] 54 func = None 55 def set_start_func(name): 56 # スタートボタンをクリックしたときの処理を、コンボボックスの選択により切り替える 57 58 # 外側のスコープにある変数を書き換える為に nonlocal 宣言 59 nonlocal func 60 61 # 以前のコールバック関数があれば解除 62 if func: 63 self.start_button.clicked.disconnect(func) 64 65 # 新しいコールバック関数を登録 66 func = getattr(self, name) 67 self.start_button.clicked.connect(func) 68 69 # 関数のソースコードを所得して表示 70 import inspect 71 src = inspect.getsource(func) 72 textarea.setText(src) 73 74 self.stop_button.clicked.connect(self.cancelThreadLoop) 75 76 combo.currentIndexChanged[str].connect(set_start_func) 77 combo.addItems(items) 78 79 vbox = QVBoxLayout() 80 vbox.addWidget(combo) 81 vbox.addWidget(label) 82 vbox.addWidget(button1) 83 vbox.addWidget(button2) 84 85 hbox = QHBoxLayout(self) 86 hbox.addLayout(vbox) 87 hbox.addWidget(textarea) 88 89 def setCount(self, count): 90 logger.debug("setCount {}".format(count)) 91 self.label.setText("Count: {}".format(count)) 92 93 94 def autoThreadPython(self): 95 import time 96 97 win = self 98 win.running = True 99 100 class MyPyThread(Thread): 101 102 def sendCount(self, num): 103 pass 104 105 def run(self): 106 for num in range(10): 107 if not win.running: 108 break 109 self.sendCount(num) # ※ サブスレッド側で実行 (操作によっては安全でない) 110 time.sleep(1) 111 112 logger.debug("Python Thread Finished") 113 114 thread = self.py_thread = MyPyThread() 115 thread.sendCount = win.setCount # XXX: ここは少しトリッキーな方法で代用、メソッド差替 116 thread.start() 117 118 119 def autoThreadQt(self): 120 121 win = self 122 win.running = True 123 124 class MyQtThread(QThread): 125 126 sendCount = pyqtSignal(int) 127 128 def run(self): 129 for num in range(10): 130 if not win.running: 131 break 132 self.sendCount.emit(num) 133 self.sleep(1) 134 135 thread = self.qt_thread = MyQtThread() 136 thread.sendCount.connect(self.setCount) # メインスレッド側で実行される 137 thread.finished.connect(lambda: logger.debug("Qt Thread Finished")) 138 thread.start() 139 140 # self.destroyed.connect(thread.terminate) 141 142 143 def autoInterruptionThreadQt(self): 144 145 win = self 146 win.running = True 147 148 class MyQtThreadEx(QThread): 149 150 sendCount = pyqtSignal(int) 151 152 def run(self): 153 for num in range(10): 154 # NOTE: QThread::requestInterruption() で中断要求 155 if self.isInterruptionRequested(): 156 break 157 self.sendCount.emit(num) 158 self.sleep(1) 159 160 thread = self.qt_thread = MyQtThreadEx() 161 thread.sendCount.connect(self.setCount) # メインスレッド側で実行される 162 thread.finished.connect(lambda: logger.debug("Qt Thread Finished")) 163 thread.start() 164 165 166def main(): 167 import sys 168 logging.basicConfig( 169 level=logging.DEBUG, 170 format="[%(levelname)8s][%(threadName)-10s] %(message)s", 171 ) 172 app = QApplication(sys.argv) 173 win = MainWindow() 174 win.resize(800, 600) 175 win.setWindowTitle("CountUp Demo (Python Thread/QThread inherit)") 176 win.show() 177 sys.exit(app.exec_()) 178 179 180if __name__ == '__main__': 181 main() 182 183

※ QThread::finished に関して、混同されそうな点を補足。
前回の autoThreadInherit の実装では、ループが終了しないので finished が来ないと書きましたが
こちらの実装では、ループは終了出来るので(メインスレッドのイベントループへ)通知されます。

スレッドをを継承した場合に、スレッドのイベントループが動いていないのとは
また状況・理由が異なる点に注意してください。


追記: 補足の説明を書いてて自分も混同してそうな感じがしてきたので、
後日確認後に訂正入れるかもしれません。

ループが終了しないとfinishedは発呼されませんが、
terminateで強制終了した場合等を考慮してませんでした。

投稿2020/06/04 17:59

編集2020/06/07 06:56
teamikl

総合スコア8742

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

私のやりたいことは、autoThreadInherit関数であると思います

スレッド内でループを使うという観点から、私もそう思います。

が autoThreadInherit はお勧めでない使い方として書いてしまったので、
混乱の原因にならないかという心配が…書き直した方が良さそうですね。


誤用ではないが・・・みたいな歯切れの悪い説明になってしまった点は

QThreadの利用方法には、歴史的な経緯があって

以前(Qt4.xの頃)は、QThread を継承して run() に独自の処理を実装するという使い方をしてました。

Qt5.x 中頃から、QThread + a worker QObject という形態で、
スレッドでもイベントループを用いる方法が推奨されるようになりました。これにより、
データのやり取りが双方向にシグナル&スロットを使い、双方のイベントループ通しで行えるようになります。

シグナル&スロットが不要な用途では、
従来の方法でスレッド内で独自のループを使う方法も残されてます。

(別質問の内容も混ざりますが)少し整理すると

Python Thread と Qt Thread については、
この場合は、Pythonのスレッドでも同じ使い方なのでQThreadを使う利点があまりないのではというのが私見です。ただし、敢えて今から変更する理由も特に思い当たりません。

QThreadの使い方2通りについて
QThread + Worker 方式と QThread 継承方式については、
独自のループを使うのであれば 後者の QThread 継承方式が良さそうです。
(実際にコードを書いてみた感じ、ループの終了とスレッドの終了が2度手間になりました)


参考

External Links 1, 2, の
"Qt Blog on subclassing QThread is wrong,"
"Woboq Blog on subclassing QThread is not always wrong,"
相反する題名があります、公式ブログの記事でも一時期誤用間違いみたいな扱いをされてたことがありました。

When to subclass and when not to?

  • If you do not really need an event loop in the thread, you should subclass.
  • If you need an event loop and handle signals and slots within the thread, you may not need to subclass.

サブクラス化が必要かどうかはイベントループが必要かどうかを基準として提示されてます。
独自のループを使う場合は、イベントループが実質制限されるので、
必然的にサブクラス化でも良いことに。

こちらはより古い文書で、autoThreadInherit の実装は
この文書中の DOs and DON'Ts で言う所の " shouldn't " の実例に当たります。


訂正: 誤用は過度な表現だったかもしれません。
デメリットのある方法として捉えられてましたが、後々
イベントループが不要な用途では許容みたいな風潮になってきた印象です。
(ずっとQt情報を追っていたわけではないので、一般ユーザの感想です)

投稿2020/06/04 13:33

編集2020/06/04 16:23
teamikl

総合スコア8742

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

①line(22~28)

func=getattr(self,label) は func=self.label と同じ意味であると勉強しました。ここでのlabelは、GUI上にある空欄のテキストボックス?であると解釈しています。

今気が付きましたが、外側の変数名と被ってしまっていて
ここのlabel は直前の def set_start_func(label): の引数に与えられる文字列になります。
(itemsのうちの選択されたもの一つ)

そこで、if func: とは何を表すのでしょうか?

また、 self.start_button.clicked.disconnect(func) と self.start_button.clicked.connect(func) はどういう意味なのでしょうか?(ボタンを押すとfuncにconnect,disconnectするのは理解しているのですが、funcがどのように動作しているものなのか分からないので、結論何が何だか分からない。となってしまいます。)

ここは大雑把に説明しますが、コンボボックスの
["autoFreeze", "autoTimer", "autoThread", "autoThreadInherit"] の選択により、
ボタンを押したときに呼び出される関数を動的に切り替えています。

if func: は、以前の選択した関数があった場合、disconnect でそれを解除します。

追記:
def set_start_func(label) の引数は、被らないように変更した方が良かったです。
label -> name 2か所

  • def set_start_func(name)
  • func = getattr(self, name)

追記2:

func=getattr(self,label) は func=self.label と同じ意味であると勉強しました

正確には self.label と同じなのは getattr(self, "label") です。
ここでの label は文字列の変数で、任意の文字列 ("autoTimer" 等) を取り、
MainWindowクラスの autoXXXX メソッドを動的に選んでます。


②line(161~209)

ここにある while 1: (line 185) の説明に「このループは強制終了しか終了手段がない」とありますが、強制終了というのは、メインスレッドからの run()=Folse による run() の上書きでできるのでしょうか?

できません。

QThread::terminate() で強制終了することになります。
ループを自然に抜けて終了しないと、ループ終了時の後始末がやり難い事に繋がります。


line(193~195)について

thread=self.thread=MyThread() →サブスレッドの作成を意味している?
thread.moveToThread(thread) →作成したサブスレッドへ移動している?
thread.sendCount.connect(self.setCount) →サブスレッドからメインスレッドのsetCount関数にデータを送れるようにしている?

概ねその理解の通りです。

autoThreadInheritの実装は、推奨でない使い方の例として書いたコードなので、
一応動くからと言って、このまま参考にするのはお勧めできません。
(上記の内、moveToThread は不要で、省いても動作に支障なし)


line(201,202)について

terminate = lambda: thread.terminate() は
def terminate(): ruturn thread.terminate()

と同じであると勉強しました。

このとき、line 202 の実行でサブスレッドを終了させているのでしょうか?

その通り、同じです。サブスレッドの終了です。

ここは通常のPython の関数であれば

self.stop_button.clicked.connect(thread.terminate)

と出来るはずなのですが、 thread.terminate() はスロットで、このスレッドの使い方の場合は、
thread.terminateスロットとして直接接続しても、
run()がループで上書きされてスレッドのイベントループが機能してない状態なので、
スレッド側で terminate() を呼び出す事は出来ません。

通常の関数として渡す事で、「メインスレッド側で」 terminate() を呼んでいます。
(あまり安全な終了方法ではありません)


line(205~207)について

# 終了時にストップボタンのスロット解除 @thread.finished.connect def tear_down(): self.stop_button.clicked.disconnect(terminate)

処理内容については、コメントの通りですが、
疑問点については、「デコレーター」といって、Python言語の機能です。

関数に事前処理・事後処理を追加したりするのに使えるのですが、
応用で、コールバック登録と関数定義を同時に行ってます。

書き換えるとこうなります。

python

1def tear_down(): 2 self.stop_button.clicked.disconnect(terminate) 3 4thread.finished.connect(tear_down)

追記: autoThreadInheritの finished シグナルについて

[2020/06/05] 追記訂正:

# XXX: このループは強制終了以外に終了手段がないため
# スレッド終了時のfinished シグナルは送られません

元々はシグナルが期待通りに動かない事の、実演のつもりで書いてましたが、

STOPボタンを押したときの terminate は後から追加したコードで、
ドキュメントがコード変更に追従してませんでした。

現行のコードでは、terminate() 時にループは強制終了され、
finishedシグナルが発呼されます。

投稿2020/06/04 12:21

編集2020/06/04 18:06
teamikl

総合スコア8742

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

pythonnoob1

2020/06/04 13:15

早い回答とたくさんの質問にたいっし詳細に回答していただきありがとうございます。 サブスレッドの強制終了に関してですが、強制終了が安全な終了方法でないことは理解しております。ただ、「while を使い、while から抜け出せない以上強制終了させるしかない。」ということでしょうか? また、QThreadのイベントループについて、 ①:どのくらい長い処理を行うとイベントループが止まってしまうのでしょうか? ②:例えば n=0 while n<9999: n += 1 time.sleep(1) print(str(n)) のように処理に9999秒かかる処理の後でも、その処理が終われば、イベントループは動き出すのでしょうか? ③:②のような極端な考えではなく30秒ほどの処理ならイベントループは止まらないのでしょうか?
teamikl

2020/06/04 14:12 編集

QThread の使い方に関して2通りあるのですが、 QThread を継承する方法(inherit)では、イベントループは動きません。 スレッドは起動時に run() を呼び出し、そこでイベントループを開始するのですが、 継承して使う場合、その run() メソッド自体を上書きすることになるからです。 run()内で、改めて自分で明示的にイベントループを使うことは可能ですが、 その場合自分のループとの共存がまた課題になります。 ==== Qthread + worker 方式では、 処理が終わればイベントループは動きます。 例えばthread.started.connect(worker.process) の様に登録したのであれば、 worker.process はイベントループから起動時に呼び出される関数の一つという扱いです。 > 30秒ほどの処理ならイベントループは止まらないのでしょうか? その処理30秒間イベントループは(一時的に)止まってます。 イベントループと同じスレッド上で直列に実行されてると考えてください。 イベントループ → 処理(30秒) → イベントループに戻るという流れです。 処理が終わればイベントループは動き出しますが、 問題はイベントがemit() したタイミングではなくて、時間のある処理があればその分遅れる点。 例えば、実際に困りそうな例を挙げると ドア開閉の通知を受け取り データベース等にそのログを記録するクラスを別スレッドで動かすとします。 時間が掛かる処理があり通知が送れる → 通知された時間では正確な時間は解らない、となります。 (現状の サブスレッド →メインスレッドへの doorOpened.emit() は大丈夫です、 メインスレッドのイベントループで時間のかかる処理がなければ。 ↑は、別スレッドを作ってそこで時間のかかる処理があった場合の例えです)
pythonnoob1

2020/06/04 14:27

>問題はイベントがemit() したタイミングではなくて、時間のある処理があればその分遅れる点。 「ループ処理の途中でイベントをemit()(ドアの開閉状態をメインスレッドのテキストに出力)し、その後5秒処理したのちループを抜ける」というコードを組んだ場合、メインスレッドのテキストに出力されるのはemit()した5秒後になる、ということでしょうか? ならば、 ①メインスレッドでサブスレッドを立ち上げる。 ②サブスレッドでモーターの動作時間を3秒に設定し、3秒モーターが動いたら処理を終える。(自動ドアで、ドア亜が閉まっている状態でドアの前に物体を検出できなかったら、そのまま処理を終える) ③サブスレッドを終了させる ①~③をQTimerで行う この際、テキストボックスへの出力は「開閉状態ボタン」を作成、押すことによって随時テキストボックスへ出力(QTimerを用いる) こういったことは可能でしょうか?
teamikl

2020/06/04 14:46

>「ループ処理の途中でイベントをemit()(ドアの開閉状態をメインスレッドのテキストに出力)し、その後5秒処理したのちループを抜ける」というコードを組んだ場合、メインスレッドのテキストに出力されるのはemit()した5秒後になる、ということでしょうか? いいえ。ここは末尾に書いた部分に相当するのですが、 GUIがフリーズ(メインスレッドのイベントループが停滞)してない限りは遅れはありません。 通知を受け取る側のスレッドのイベントループが渋滞していた場合の例えです。 ---- > ①メインスレッドでサブスレッドを立ち上げる。 : > ①~③をQTimerで行う 字面通りの実装だと、QTimer 内でサブスレッドを立ち上げるとなりますが? 恐らく、サブスレッドを立ち上げてその中でQTimer を使った方が良いと思います。 (パフォーマンス的にも、コード記述量的にも) 基本的にループを使った処理は QTimer で置き換え可能です。 スレッドを用いない例ですが autoFrezz と autoTimer を比較すると、解りやすいと思います。 コードの見通しやローカル変数のスコープ等が若干変わってしまいますが ループの中身の処理を timer で毎回呼び出すようにコードを変更。
pythonnoob1

2020/06/05 11:27

返信が遅れてしまい、大変申し訳ございません。 前回の質問への回答と、回答3のコードについて、ありがとうございました。大変見やすかったです。 QTimerの使い方について質問です。 QTimerは「connectした関数を下のstart()で指定した秒数毎に実行する」ものだと解釈しています。 そこで、 ①start()で指定した秒数(ミリ秒)よりも、実行する関数の処理時間が長い場合、どのようなことが起こるのでしょうか?普通にエラーが出るのでしょうか? ②例えば、実行したい関数が ---- def auto(): if (物体を検知): (10秒の処理) pass else: #(物体を検知しなかった) pass ---- で、start(1000)にしたとします。 この時、else(物体が検知しなかった)とき、次、この関数が実行されるまでの10秒間何も処理をされないことになるのですか?(私は何もしていないと思っています)
teamikl

2020/06/05 11:49

> ①start()で指定した秒数(ミリ秒)よりも、実行する関数の処理時間が長い場合、どのようなことが起こるのでしょうか?普通にエラーが出るのでしょうか? この辺りは、実際にコードを書いて試してみた方が良いでしょう。 エラーではありませんが、Timerの使い方として誤りで Timerに指定する時間は、○○秒経過以降であれば実行で、 必ず一定間隔での呼び出しを保証するものではありません。 関数を呼び出すのはそのスレッドのイベントループなので、 一定間隔に実行するタイマーとして使うには、イベントループが遅延なく動いてるのが前提となります。
pythonnoob1

2020/06/05 13:03

QTimerの動作確認プログラムありがとうございます。 普段Atomエディタを使用してコードを書いています。CTRL+Rを押しても実行されません(Atom-runningをインストールしているのですが…)。 明日になればラズパイを手元におけるので動作確認が出来るはずです。 大変申し訳ありません。 その上で質問です。下の関数は前の質問で使用したものです。 ---- def auto(): if (物体を検知): (10秒の処理) pass else: #(物体を検知しなかった) pass ---- ここで、start(500)にしたとします。 ・物体が検知されたら 10秒の処理中(イベントループが止まる) 処理終了(イベントループが動き出す) 0.5秒後、再度関数が呼び出される ・物体を感知しなかったら そのまま、イベントループが止まらないので0.5秒後に再度関数が呼び出される ということができるのでしょうか?
teamikl

2020/06/05 13:50 編集

できますが、イベントループを止める以上は敢えてタイマーを使う必要がなくなり、 普通に auto() 関数内の処理をループにして、0.5秒待つで良くなります。 シグナル&スロットを使う為にタイマーにしたい場合は、 時間のかかるループ処理は分割して実行するように、 イベントループ上での作法に従う必要があります。 ここで、以前の関数をそのまま持ってきただけでは、 イベントループが止まりシグナル&スロットは期待通りに動かなく、本末転倒になってしまいます。 一応、イベントループを動かしつつ自分のループも動かす方法はあるのですが、 お勧めはしません。(一応サンプルだけタイマーのコードに追加します ---- 実機以外でPythonの開発環境は構築されてないのですか? 普通にコマンドラインでファイル名を指定して実行でも良いので、 テスト実行できる環境は準備しておいた方が良いです。 追記→ブラウザで実行を確認できるようにrepl.it にコードを載せました
pythonnoob1

2020/06/05 14:15

QTimerを使うより、QThreadを使う方向で進めていったほうがよさそうですね… teamiklさんのおかげで、だいぶ容量をつかめてきたと思います(思いたいです…) また後日自分なりに、今まで得た知識の中でコードを組んでみたいと思いますもで、もしよろしければ、添削を再度お願いできないでしょうか? repi.itの件、ありがとうございました。動作確認できました。 動作確認できる環境を準備しておきます。
teamikl

2020/06/07 06:44

こちらでは簡潔に通知のみ。markdownを使う為、別回答にて返信しました。 後、他の回答の更新: スレッド中断のサンプルコードをもうひとつ追加と repl.it へ掲載。
pythonnoob1

2020/06/07 07:35

ありがとうございます。 動作確認させていただきました。 Pythonスレッドではサブスレッドで、QThreadスレッドではメインスレッドでsetCountが実行されているのが確認できました。 ただ、autoThreadQt関数、autoInterruptionThreadQt関数の違いが分かりません。 実行されているsetCountの終了の仕方が2種類あるということなのでしょうか? また、例えば「x=1のときテキストボックス(label)にAを、x=2のときBを、その他のときCを出力できる」ようにしたいです。 ------- x=1 while 1: if x==1: (labelにAを出力) x += 1 elif x==2: (labelにBを出力) x += 1 else: (labelにCを出力) x = x - 2 --------- のようなループ関数を用いた場合どうコードに組み込んだらよいのでしょうか?
teamikl

2020/06/07 08:41 編集

>ただ、autoThreadQt関数、autoInterruptionThreadQt関数の違いが分かりません。 >実行されているsetCountの終了の仕方が2種類あるということなのでしょうか? 実は、見た通り殆ど違いはありません。cancelThreadLoop() 内でのスレッドの中断方法で、 runningフラグで管理していた部分を、QThread内部のフラグを使うように変更したのみです。 一応、後者はスレッドセーフな操作になります。 ですが、ほかのへんすうあまりスレッドセーフを気にしなくても良いことが多いので、 「"ループの中断のロジック"は自分で組み込む必要がある」という点さえ押さえておけば大丈夫です。 ---- スレッド側のコードで、シグナルを使います (emit を呼んでる部分) シグナルの定義で sendString = pyqtSignal(str) を作れば、文字列など任意のデータを送れます、
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問