回答編集履歴
4
文章の修性
answer
CHANGED
@@ -29,11 +29,11 @@
|
|
29
29
|
スレッドとTkinter の生存期間について、
|
30
30
|
スレッド内からTkinter へ直接アクセスが発生しているが、
|
31
31
|
Tkinter のイベントループ終了後にもGUIへのアクセスが発生する。
|
32
|
-
対策A =>
|
32
|
+
対策A => WM_DELETE_WINDOW のハンドラで、tkのイベントループ終了前にスレッドを終了する。
|
33
33
|
対策B => 独自のqueue 経由して GUI 更新を処理する。
|
34
34
|
|
35
35
|
----
|
36
|
-
解決策: メインスレッド側でGUIの更新をする
|
36
|
+
解決策1-1: メインスレッド側でGUIの更新をする
|
37
37
|
|
38
38
|
完全なの方法ではありませんが、一般的なケースで
|
39
39
|
tkinterの場合は、after_idle に関数・引数を渡す事で
|
@@ -58,8 +58,7 @@
|
|
58
58
|
要: queue を使ったスレッド間通信。
|
59
59
|
|
60
60
|
|
61
|
-
解決策2:
|
62
|
-
simpledialog.Dialog の実装を使う。
|
61
|
+
解決策2: simpledialog.Dialog の実装を使う。
|
63
62
|
|
64
63
|
解決策3:
|
65
64
|
因みに、時刻の更新表示のようなものならスレッドを使わなくても
|
3
3つ目の問題点追記により文章を修正
answer
CHANGED
@@ -8,11 +8,11 @@
|
|
8
8
|
一見うまく動いているように見えても、タイミング次第でエラーになる事もあるので、
|
9
9
|
どの操作がスレッドセーフなのか、そうでないのかは見極めが難しい箇所です。
|
10
10
|
|
11
|
-
以下は、
|
11
|
+
以下は、3つのトピックがあるので注意してください。
|
12
12
|
- GUIでのマルチスレッドで、スレッド側からGUIを更新する方法
|
13
13
|
- SimpleDialogの実装の問題点
|
14
|
+
- アプリ終了時に、スレッドが終了済みのGUIへアクセスする問題
|
14
15
|
|
15
|
-
|
16
16
|
問題点: 別スレッドからのGUI操作
|
17
17
|
```
|
18
18
|
self.clock["text"] = datetime.now().strftime("%Y/%m/%d %H:%M:%S.%f")
|
2
問題点3を追記
answer
CHANGED
@@ -24,6 +24,14 @@
|
|
24
24
|
simpledialog.Dialog や messagebox.askyesno では発生しません。
|
25
25
|
(アプリ終了時にエラーは出ますが、別件です。今回はダイアログを閉じた時のエラーのみを焦点)
|
26
26
|
|
27
|
+
問題点3:
|
28
|
+
|
29
|
+
スレッドとTkinter の生存期間について、
|
30
|
+
スレッド内からTkinter へ直接アクセスが発生しているが、
|
31
|
+
Tkinter のイベントループ終了後にもGUIへのアクセスが発生する。
|
32
|
+
対策A => WM_WINDOW_DELETE のハンドラで、tkのイベントループ終了前にスレッドを終了する。
|
33
|
+
対策B => 独自のqueue 経由して GUI 更新を処理する。
|
34
|
+
|
27
35
|
----
|
28
36
|
解決策: メインスレッド側でGUIの更新をする
|
29
37
|
|
1
タイマー(after) でのサンプルを追加
answer
CHANGED
@@ -59,6 +59,87 @@
|
|
59
59
|
|
60
60
|
スレッドを他の用途に使う必要がなければ、タイマーが一番無難な解決策です。
|
61
61
|
|
62
|
+
追記: タイマーでの実装。(※スレッドで扱う場合の問題解決にはなりません)
|
63
|
+
```python
|
64
|
+
#!/usr/bin/env python3.8
|
65
|
+
|
66
|
+
import datetime
|
67
|
+
import tkinter as tk
|
68
|
+
from tkinter import ttk
|
69
|
+
import mydialog # https://teratail.com/questions/275549 質問文から借用
|
70
|
+
|
71
|
+
|
72
|
+
def getTimestamp(_time_format="%Y/%m/%d %H:%M:%S.%f"):
|
73
|
+
return datetime.datetime.now().strftime(_time_format)
|
74
|
+
|
75
|
+
|
76
|
+
class App:
|
77
|
+
def __init__(self, win):
|
78
|
+
self.win = win
|
79
|
+
self._running = False
|
80
|
+
self._timer = None
|
81
|
+
self._init_widgets()
|
82
|
+
self._init_events()
|
83
|
+
|
84
|
+
def _init_widgets(self):
|
85
|
+
time_text = self._time_text = tk.StringVar(self.win)
|
86
|
+
input_text = self._input_text = tk.StringVar(self.win)
|
87
|
+
frame = self._frame = ttk.Frame(self.win)
|
88
|
+
label = self._label = ttk.Label(frame, textvar=time_text)
|
89
|
+
entry = self._entry = ttk.Entry(frame, textvar=input_text)
|
90
|
+
stop_button = self._stop_button = ttk.Button(frame, text="Stop")
|
91
|
+
start_button = self._start_button = ttk.Button(frame, text="Start")
|
92
|
+
|
93
|
+
frame.pack(fill=tk.BOTH, expand=True)
|
94
|
+
label.pack()
|
95
|
+
entry.pack()
|
96
|
+
stop_button.pack()
|
97
|
+
start_button.pack()
|
98
|
+
|
99
|
+
def start_timer(self):
|
100
|
+
assert not self._running
|
101
|
+
self._running = True
|
102
|
+
self._timer = self.win.after_idle(self._on_timer)
|
103
|
+
|
104
|
+
def _init_events(self):
|
105
|
+
self._stop_button.bind("<Button-1>", self._on_stop_timer)
|
106
|
+
self._start_button.bind("<Button-1>", self._on_start_timer)
|
107
|
+
|
108
|
+
def _on_timer(self):
|
109
|
+
if not self._running and self._timer:
|
110
|
+
self.win.after_cancel(self._timer)
|
111
|
+
self._timer = None
|
112
|
+
return
|
113
|
+
|
114
|
+
self._time_text.set(getTimestamp())
|
115
|
+
|
116
|
+
# NOTE: 60FPS: 16.666 (1000/60)
|
117
|
+
# あまり小さくし過ぎても無効な値になります。
|
118
|
+
self._timer = self.win.after(15, self._on_timer)
|
119
|
+
|
120
|
+
def _on_start_timer(self, event):
|
121
|
+
if not self._running:
|
122
|
+
self.start_timer()
|
123
|
+
|
124
|
+
def _on_stop_timer(self, event):
|
125
|
+
import mydialog
|
126
|
+
if not self._input_text.get():
|
127
|
+
ret = mydialog.askyesno(message="中断します", parent=self.win)
|
128
|
+
if ret == "OK":
|
129
|
+
self._running = False
|
130
|
+
|
131
|
+
|
132
|
+
def main():
|
133
|
+
win = tk.Tk()
|
134
|
+
app = App(win)
|
135
|
+
app.start_timer()
|
136
|
+
win.mainloop()
|
137
|
+
|
138
|
+
|
139
|
+
if __name__ == '__main__':
|
140
|
+
main()
|
141
|
+
```
|
142
|
+
|
62
143
|
----
|
63
144
|
「スレッド側でダイアログを開いて更にモーダルにしたい」
|
64
145
|
みたいな時は、更にスレッドに配慮した工夫が必要になります。
|