回答編集履歴
4
説明文の修正
answer
CHANGED
@@ -31,7 +31,7 @@
|
|
31
31
|
(このサンプルでは問題ありませんが、場合によってはプログラムがクラッシュしたりします)
|
32
32
|
|
33
33
|
QThread を使う方法では、
|
34
|
-
メインスレッドのイベントループ
|
34
|
+
メインスレッドのイベントループが順番に処理する為、
|
35
35
|
同時アクセスは発生せずロックは不要です。(シグナル&スロットとイベントループを利用する恩恵)
|
36
36
|
|
37
37
|
|
3
リンクの修正と説明の追記(サンプルコードのロギングでスレッド名を表示)
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 を使ってスレッドを中断するサンプルを追加
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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:
|
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
スレッド確認のコードを追加(コメントアウト)
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
|
|