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

回答編集履歴

4

文章の修性

2020/07/07 04:04

投稿

teamikl
teamikl

スコア8817

answer CHANGED
@@ -29,11 +29,11 @@
29
29
  スレッドとTkinter の生存期間について、
30
30
  スレッド内からTkinter へ直接アクセスが発生しているが、
31
31
  Tkinter のイベントループ終了後にもGUIへのアクセスが発生する。
32
- 対策A => WM_WINDOW_DELETE のハンドラで、tkのイベントループ終了前にスレッドを終了する。
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つ目の問題点追記により文章を修正

2020/07/07 04:04

投稿

teamikl
teamikl

スコア8817

answer CHANGED
@@ -8,11 +8,11 @@
8
8
  一見うまく動いているように見えても、タイミング次第でエラーになる事もあるので、
9
9
  どの操作がスレッドセーフなのか、そうでないのかは見極めが難しい箇所です。
10
10
 
11
- 以下は、つのトピックがあるので注意してください。
11
+ 以下は、つのトピックがあるので注意してください。
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を追記

2020/07/07 04:01

投稿

teamikl
teamikl

スコア8817

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) でのサンプルを追加

2020/07/07 03:58

投稿

teamikl
teamikl

スコア8817

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
  みたいな時は、更にスレッドに配慮した工夫が必要になります。