質問をすることでしか得られない、回答やアドバイスがある。

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

新規登録して質問してみよう
ただいま回答率
85.48%
Python 3.x

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

while

Whileは多くの言語で使われるコントロール構造であり、特定の条件が満たされる限り一連の命令を繰り返し実行します。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

2回答

5753閲覧

QThreadの使い方を教えて下さい

pythonnoob1

総合スコア18

Python 3.x

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

while

Whileは多くの言語で使われるコントロール構造であり、特定の条件が満たされる限り一連の命令を繰り返し実行します。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

2クリップ

投稿2020/05/22 08:38

編集2020/05/23 09:01

前提

Python 3.x
RaspberryPi 3 B
PyQt5

やりたいこと

「GUI上で自動ドアを操作したい」

自動ボタンを押す
①焦電センサでモノを検知
②リードスイッチ2が反応するまでモーターを正転
③モーター停止
④リードスイッチ1が反応するまでモータを逆転
⑤モーター停止
①~⑤がループ

停止ボタンを押す
モーターが停止する

状況

下のようなコードを作成し、自動ボタンを押したところGUIが固まってしまいました。
これについて、「GUIアプリケーションはメインスレッドでイベントループが動いており、while を使うとイベントループが止まってしまうため GUI が動かなくなる」ということを教えていただきました。
また、それを回避するにはサブスレッドを作成して、そこで while を使うといいと教えていただきました。

そこで、私なりに調べてみたところサブスレッドを作るために QThread か QtConcurrent を使うとよいと書かれていました。しかし、使い方がいまいち分かりませんでした。
分からないことが多すぎて、**質問が大枠になってしまいますが QThread か QtConcurrent の使い方をご教授ください。
**
また while を使わなくても、処理をループさせる方法などがあればご教授ください。

無知な私ですがよろしくお願いします。
ただ、現在手元に動作確認できる機材がないため、下のコードがどのように動くか不明です。

python import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * from PyQt5.QtCore import * import subprocess import time import numpy as np import cv2 import RPi.GPIO as GPIO Shoden_PIN=22 Motor_PIN_1=24 Motor_PIN_2=25 Reed_PIN_1=17 Reed_PIN_2=27 GPIO.setmode(GPIO.BCM) GPIO.setup(Shoden_PIN, GPIO.IN) GPIO.setup(Reed_PIN_1, GPIO.IN) GPIO.setup(Reed_PIN_2, GPIO.IN) def _init_GPIO(): GPIO.setmode(GPIO.BCM) GPIO.setup(Motor_PIN_1, GPIO.OUT) GPIO.setup(Motor_PIN_2, GPIO.OUT) def Motor_Forward(): _init_GPIO() GPIO.output(Motor_PIN_1, 1) GPIO.output(Motor_PIN_2, 0) def Motor_Backward(): _init_GPIO() GPIO.output(Motor_PIN_1, 0) GPIO.output(Motor_PIN_2, 1) def Motor_Stop(): _init_GPIO() GPIO.output(Motor_PIN_1, 0) GPIO.output(Motor_PIN_2, 0) class Tab1Widget(QWidget): def __init__(self): super().__init__() self.title = "GUI test" self.left = 10 self.top = 10 self.width = 640 self.height = 480 self.initUI() self.counter = 0 def initUI(self): super(Tab1Widget, self).__init__() btn1 = QPushButton("自動", self) btn2 = QPushButton("停止", self) btn1.clicked.connect(self.auto ) btn2.clicked.connect(self.stop ) self.textbox4 = QLineEdit(self) label3 = QLabel("自動ドア") label4 = QLabel("ドアの状態") layoutA = QGridLayout() layoutA.addWidget(label4,0,0) layoutA.addWidget(self.textbox4,0,1) layoutA.addWidget(btn1,1,0) layoutA.addWidget(btn2,1,1) layoutB = QVBoxLayout() layoutB.addWidget(label3) layoutB.addLayout(layoutA) self.setLayout(layoutB) self.show() ''' 焦電センサが物体を検知 モーターが正転 リードスイッチ2が反応 モーター停止 モーターが逆転 リードスイッチ1が反応 モーター停止 ''' def auto(self): if __name__ == '__main__': try: while 1: self.textbox4.setText("CLOSE") if GPIO.input(Shoden_PIN)==GPIO.HIGH: self.textbox4.setText("OPEN") Motor_Forward() while 1: if GPIO.input(Reed_PIN_2)==GPIO.HIGH: GPIO.cleanup() Motor_Stop() time.sleep(1) GPIO.cleanup() Motor_Backward() while 1: if GPIO.input(Reed_PIN_1)==GPIO.HIGH: self.textbox4.setText("CLOSE") GPIO.cleanup() Motor_Stop() GPIO.cleanup() break else: pass break else: pass else: pass except Exception as e: print("Error") finally: GPIO.cleanup() print("GPIO clean") def stop(self): Motor_Stop() GPIO.cleanup() if __name__ == "__main__": app = QApplication(sys.argv) ex = Tab1Widget() sys.exit(app.exec_())

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

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

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

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

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

guest

回答2

0

イメージ説明

GPIOは別にして、本題の回答 QThreadの使い方、折角書いたので投稿します。
スレッドとタイマーを使ったデモです。(説明はソース内)

  • autoFreeze ... 現状のGUIが固まる実装 -> 原因の把握
  • autoTimer ... QTimer を使った回避策 -> whileループ から Timer の書き換え
  • autoThread ... QThread のイベントループ上で QTimer を実行
  • autoThreadInherit ... QThread上でループ (問題点を提示する為の比較用)

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 225

投稿2020/05/25 04:45

teamikl

総合スコア8664

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

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

pythonnoob1

2020/05/25 23:55

返信が遅れてしまい、大変申し訳ありません。 とても参考になりました。 まだ、初心者ですが、今後私もteamikl様のように、分からないことを教えてあげられる存在になっていきたいと思います!! ありがとうございました!!
pythonnoob1

2020/06/03 11:48

teamilkさんの作成してくださったコードを拝見し、勉強させていただきました。 そこで、分からないことが何点か、出てきましたので質問させてください。 また、質問っするにあたって、このコメント欄では、コードを書くことが出来ません。 大変お手数をおかけしますが、私をフォローしていただき、回答リクエストを送らせてください。
guest

0

ベストアンサー

QThread にもイベントループがあり、
QThread 内でもループを使うと、(main thread ではなければGUIを止めることはありませんが)
イベントループの処理が止まってしまう問題もあります。

スレッド側のイベントループは使わない方法もありますが、
その場合は、扱いはPythonのスレッドとほぼ同じです。

while を使わなくても、処理をループさせる方法

イベントループを止めずに、○秒毎に処理を行いたい場合はQTimerを使います。


他の解決策:

GPIO は使える環境にないので、詳しくないのですが、

add_event_detect, add_event_callback 等の、
イベント時に、コールバック関数を呼び出してくれる仕組みがあるようなので、

ポーリング方式 (1秒毎にチェックする) より、
割り込み方式(状態が変化したときに通知)を検討してみてはどうでしょう。

内部的には、GPIO側でスレッド (※ pthread) が起動するので、
メインスレッドのQt のイベントループを阻害しないはずです。(実際に、試したことはありません

RPi.GPIO Threaded callbacks

RPi.GPIO runs a second thread for callback functions.

投稿2020/05/24 08:55

teamikl

総合スコア8664

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

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

pythonnoob1

2020/05/24 09:11

回答いただき、誠に有難うございます。 現在、質問本文でも記載してます通り、実行を試せる機材が手元にない状況です。(コロナのせいです。苦笑) 6月になったら動作確認が出来ると思いますので、teamikl様の回答を参考にさせていただきます。 ありがとうございました!!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問