teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

4

説明文の修正

2020/06/07 06:56

投稿

teamikl
teamikl

スコア8817

answer CHANGED
@@ -31,7 +31,7 @@
31
31
  (このサンプルでは問題ありませんが、場合によってはプログラムがクラッシュしたりします)
32
32
 
33
33
  QThread を使う方法では、
34
- メインスレッドのイベントループを通じてシグナルを順番に処理する為、
34
+ メインスレッドのイベントループ順番に処理する為、
35
35
  同時アクセスは発生せずロックは不要です。(シグナル&スロットとイベントループを利用する恩恵)
36
36
 
37
37
 

3

リンクの修正と説明の追記(サンプルコードのロギングでスレッド名を表示)

2020/06/07 06:56

投稿

teamikl
teamikl

スコア8817

answer CHANGED
@@ -17,10 +17,25 @@
17
17
  QThreadの方が、オブジェクトの管理をQtの仕組みに任せられるのと、
18
18
  片方向ですがシグナル&スロットを使える分利点が多そうでした。
19
19
 
20
- 追記: repl.it 上にコードを載せました。
20
+ ** [2020-06-07] 追記:** repl.it 上にコードを載せました。
21
21
  ソースコメント内の日本語は文字化けしますが、ブラウザ上で実行可能です。
22
22
 
23
+ ロギングでどのスレッドで実行されてるかを解るようにしたので、
24
+ **setCountがどのスレッドから実行されてるか辺りを着目してください**
25
+
26
+ - Python スレッド ... サブスレッドで実行(スレッドセーフでない)
27
+ - QThread スレッド ... メインスレッドで実行 (スレッドセーフ)
28
+
29
+ 前者の場合は、より安全にするなら追加でロック機構
30
+ 複数のスレッドから同時にアクセスさせないようにする仕組みの導入が必要です。
31
+ (このサンプルでは問題ありませんが、場合によってはプログラムがクラッシュしたりします)
32
+
33
+ QThread を使う方法では、
34
+ メインスレッドのイベントループを通じてシグナルを順番に処理する為、
35
+ 同時アクセスは発生せずロックは不要です。(シグナル&スロットとイベントループを利用する恩恵)
36
+
37
+
23
- https://repl.it/@MiKLTea/QThread-Demo
38
+ [[source on repl.it]](https://repl.it/@MiKLTea/QThread-Demo)
24
39
  ```python
25
40
  #!/usr/bin/env python3.8
26
41
 

2

QThread::requestInterruption を使ってスレッドを中断するサンプルを追加

2020/06/07 06:55

投稿

teamikl
teamikl

スコア8817

answer CHANGED
@@ -17,9 +17,14 @@
17
17
  QThreadの方が、オブジェクトの管理をQtの仕組みに任せられるのと、
18
18
  片方向ですがシグナル&スロットを使える分利点が多そうでした。
19
19
 
20
+ 追記: repl.it 上にコードを載せました。
21
+ ソースコメント内の日本語は文字化けしますが、ブラウザ上で実行可能です。
20
22
 
23
+ https://repl.it/@MiKLTea/QThread-Demo
24
+ ```python
25
+ #!/usr/bin/env python3.8
21
26
 
22
- ```python
27
+ import logging
23
28
  from threading import Thread, current_thread
24
29
 
25
30
  from PyQt5.QtCore import Qt, QObject, QTimer, QThread, pyqtSignal, pyqtSlot
@@ -28,7 +33,9 @@
28
33
  QPushButton, QLabel, QTextBrowser, QComboBox,
29
34
  QHBoxLayout, QVBoxLayout)
30
35
 
36
+ logger = logging.getLogger(__name__)
31
37
 
38
+
32
39
  class MainWindow(QWidget):
33
40
  def __init__(self, parent=None):
34
41
  super().__init__(parent)
@@ -37,7 +44,6 @@
37
44
  self.running = False
38
45
  self.initUI()
39
46
 
40
-
41
47
  def closeEvent(self, event):
42
48
  self.cancelThreadLoop()
43
49
 
@@ -51,9 +57,16 @@
51
57
  super().closeEvent(event)
52
58
 
53
59
  def cancelThreadLoop(self):
54
- print("cancel")
60
+ logger.debug("cancel")
61
+
62
+ # autoThreadPython, autoThreadQt の中断。
63
+ # TODO: このコードは Thread-Safe でないので、Mutexを使った方が良い。
55
64
  self.running = False
56
65
 
66
+ if self.qt_thread:
67
+ # autoInterruptionThreadQt の中断
68
+ self.qt_thread.requestInterruption()
69
+
57
70
  def initUI(self):
58
71
  label = self.label = QLabel("")
59
72
  button1 = self.start_button = QPushButton("Start")
@@ -61,7 +74,7 @@
61
74
  combo = QComboBox()
62
75
  textarea = QTextBrowser()
63
76
 
64
- items = ["autoThreadPython", "autoThreadQt"]
77
+ items = ["autoThreadPython", "autoThreadQt", "autoInterruptionThreadQt"]
65
78
  func = None
66
79
  def set_start_func(name):
67
80
  # スタートボタンをクリックしたときの処理を、コンボボックスの選択により切り替える
@@ -98,8 +111,7 @@
98
111
  hbox.addWidget(textarea)
99
112
 
100
113
  def setCount(self, count):
101
- # 呼び出されたスレッドの確認
102
- # print("setCount", count, current_thread())
114
+ logger.debug("setCount {}".format(count))
103
115
  self.label.setText("Count: {}".format(count))
104
116
 
105
117
 
@@ -121,7 +133,7 @@
121
133
  self.sendCount(num) # ※ サブスレッド側で実行 (操作によっては安全でない)
122
134
  time.sleep(1)
123
135
 
124
- print("Python Thread Finished")
136
+ logger.debug("Python Thread Finished")
125
137
 
126
138
  thread = self.py_thread = MyPyThread()
127
139
  thread.sendCount = win.setCount # XXX: ここは少しトリッキーな方法で代用、メソッド差替
@@ -146,14 +158,41 @@
146
158
 
147
159
  thread = self.qt_thread = MyQtThread()
148
160
  thread.sendCount.connect(self.setCount) # メインスレッド側で実行される
149
- thread.finished.connect(lambda: print("Qt Thread Finished"))
161
+ thread.finished.connect(lambda: logger.debug("Qt Thread Finished"))
150
162
  thread.start()
151
163
 
152
164
  # self.destroyed.connect(thread.terminate)
153
165
 
154
166
 
167
+ def autoInterruptionThreadQt(self):
168
+
169
+ win = self
170
+ win.running = True
171
+
172
+ class MyQtThreadEx(QThread):
173
+
174
+ sendCount = pyqtSignal(int)
175
+
176
+ def run(self):
177
+ for num in range(10):
178
+ # NOTE: QThread::requestInterruption() で中断要求
179
+ if self.isInterruptionRequested():
180
+ break
181
+ self.sendCount.emit(num)
182
+ self.sleep(1)
183
+
184
+ thread = self.qt_thread = MyQtThreadEx()
185
+ thread.sendCount.connect(self.setCount) # メインスレッド側で実行される
186
+ thread.finished.connect(lambda: logger.debug("Qt Thread Finished"))
187
+ thread.start()
188
+
189
+
155
190
  def main():
156
191
  import sys
192
+ logging.basicConfig(
193
+ level=logging.DEBUG,
194
+ format="[%(levelname)8s][%(threadName)-10s] %(message)s",
195
+ )
157
196
  app = QApplication(sys.argv)
158
197
  win = MainWindow()
159
198
  win.resize(800, 600)
@@ -164,6 +203,8 @@
164
203
 
165
204
  if __name__ == '__main__':
166
205
  main()
206
+
207
+
167
208
  ```
168
209
 
169
210
  ※ QThread::finished に関して、混同されそうな点を補足。

1

スレッド確認のコードを追加(コメントアウト)

2020/06/07 06:41

投稿

teamikl
teamikl

スコア8817

answer CHANGED
@@ -98,6 +98,8 @@
98
98
  hbox.addWidget(textarea)
99
99
 
100
100
  def setCount(self, count):
101
+ # 呼び出されたスレッドの確認
102
+ # print("setCount", count, current_thread())
101
103
  self.label.setText("Count: {}".format(count))
102
104
 
103
105