Pythonでリアルタイム解析を行うためにthreadを使って図の更新とボタンの監視をしているのですが、右上×ボタンを押したときに
Exception ignored in: <function Image.del at 0x02D448E0>
Traceback (most recent call last):
File "C:\Users\xxxxxxxxxx\AppData\Local\Programs\Python\Python38-32\lib\tkinter_init_.py", line 4014, in del
RuntimeError: main thread is not in main loop
Tcl_AsyncDelete: async handler deleted by the wrong thread
と言われてしまいます。
これの対処法は何かございますでしょうか?
問題点となる箇所のみ取り出してコードを記述させていただきます。
Python
1 # ライブラリ 2import tkinter as tk 3import tkinter.ttk as ttk 4import threading 5import queue 6import time 7import matplotlib.pyplot as plt 8 9 10####################main###################################################################################################################################### 11 12class MyThread(tk.Frame): 13 threadnum=0 14 15 16 def DrawingValue(self): 17 if self.threadnum==1: 18 self.threadnum=0 19 else: 20 self.threadnum=1 21 22 23 24 def handle_close(self,evt): 25 print("endGUI") 26 self.threadnum=2#グラフを閉じるボタンで消した場合 27 28 29 30 def worker1(self,q): 31 savedata=0#グラフが表示されているか否か 32 33 while 1: 34 if self.threadnum==1: 35 if savedata==0:#グラフが表示されていない場合初期設定を行う 36 savedata=1 37 j=0 38 k=0 39 sinx=[] 40 rangey=list(range(0,1000)) 41 self.fig, self.ax = plt.subplots() 42 self.fig.canvas.draw() 43 bg = self.fig.canvas.copy_from_bbox(self.ax.bbox) 44 line, = self.ax.plot(rangey) 45 self.fig.canvas.mpl_connect('close_event', self.handle_close) 46 self.fig.show() 47 savedata=1 48 while j<1000: 49 sinx.append(0) 50 j+=1 51 52 while self.threadnum==1: 53 if k<1000: 54 k=k+1 55 else: 56 k=0 57 del sinx[0]#先頭を一文字消去 58 sinx.append(k)#末尾に読み出したデータを挿入 59 line.set_ydata(sinx) 60 self.fig.canvas.restore_region(bg) 61 self.ax.draw_artist(line) 62 self.fig.canvas.blit(self.ax.bbox) 63 self.fig.canvas.flush_events() 64 elif self.threadnum==2:#グラフを閉じるボタンで消した場合 65 savedata=0 66 elif self.threadnum==3:#メインを閉じた場合 67 if savedata==1: 68 plt.close() 69 break 70 else: 71 if savedata==1: 72 self.fig.canvas.flush_events()#描画停止時にフリーズを防ぐため 73 time.sleep(0.1) 74 75 76 def __init__(self,master): 77 super().__init__(master, bg='PaleTurquoise1') 78 self.plmi=0 79 80 self.rowconfigure(0, weight=1) 81 self.columnconfigure(0, weight=1) 82 83 # フレームを作成 84 frame_2 = tk.Frame(master=self, relief="groove", bd=3) 85 frame_2.rowconfigure(0, weight=1) 86 frame_2.rowconfigure(1, weight=1) 87 frame_2.columnconfigure(0, weight=1) 88 frame_2.columnconfigure(1, weight=1) 89 90 91 Button3 = tk.Button(frame_2,text=u'描画', command=self.DrawingValue) 92 Button3.grid(row=4, column=0,padx=15, pady=5) 93 frame_2.grid(sticky=tk.NSEW) 94 95 q = queue.Queue() 96 self.t1 = threading.Thread(target=self.worker1, args=(q,), daemon=True) 97 self.t1.start() 98 99 100############################################################################################################## 101 102class Controller(tk.Frame): 103 104 def closingGUI(self): 105 self.Thread1.threadnum=3#※ 106 self.Thread1.t1.join()#※ 107 self.master.destroy() 108 self.master.quit() 109 print("endALL") 110 111 def __init__(self, master): 112 super().__init__(master) 113 self.pack() 114 115 #スレッド1の作成 116 self.Thread1 = MyThread(master=self) 117 self.Thread1.grid(row=0, column=1, sticky=tk.NSEW) 118 119 120 121if __name__ == "__main__": 122 root=tk.Tk() 123 root.title(u"test") 124 app = Controller(master=root) 125 root.protocol("WM_DELETE_WINDOW", app.closingGUI) 126 app.mainloop()
色々試してみたのですが、
スレッドの終了処理(コード内※印)を行わずにデーモンスレッドで終了させたときにはこのエラーが発生しませんでした。
また、描画を行わず即時終了した時もエラーは発生しませんでした。
どこまで削除したらエラーが発生しないかとworker1内を削って確かめた結果、self.fig, self.ax = plt.subplots()を削除した場合エラーが発現しませんでした。
最悪デーモンスレッドで終了でもいいのですが、図を表示したまま終了させたときにエラーが出てしまうので何とかさせたいです、よろしくお願いします。
#####追記####################
追記前よりクローズ処理は実装しておりましたので、GUIの直接操作が問題なのかな?でもGUI直接操作しているかな?と思い、teamikl様の回答をもとに少し触ってみたのですが、「fig, ax = plt.subplots()」や「fig = plt.figure()」をサブスレッドで実行する場合にRuntimeErrorが発生するようでした。print("endworler")も書き出されているのでスレッドは終了しているはずです。
サブスレッドでGUIを動かすべきでないというのはTkinterの話だと思っていたのですが、Matplotlibも実行すべきでないのでしょうか?
元々のプログラムは1ずつ上昇してるグラフの代わりに受信したデータをリアルタイムで表示するようにしているため、メインスレッドはメインループ、サブスレッドはリアルタイム描画用としておりました。
できるだけ早いグラフ更新(2ms更新を目指していたが受信都度更新だと10msが限界だなあと感じていたところ)を目指していたのであまり処理が長くなるのは避けたかったのですが、サブスレッドはデータの受信のみとし、メインに受信データを渡してグラフ描画としなければならないということでしょうか?
別件となりますが、スレッドに描画を任せた理由はリアルタイム処理のためにメインスレッドをループさせるとGUIが操作できないから、というのを忘れてメインスレッドをループさせて描画・更新をさせたのですが
メインスレッドがWhileループ内にいるにもかかわらず、GUIの操作ができ、ボタンも反応しました。(おそらく)self.fig.canvas.flush_events()がループに存在しているのが原因だと思われるのですが、なぜGUIの操作ができるのでしょうか
python
1import tkinter as tk 2import tkinter.ttk as ttk 3from threading import Thread 4import matplotlib.pyplot as plt 5 6 7def worker(root): 8 print("startworler") 9 #fig, ax = plt.subplots()#←これの有無でエラー 10 #fig = plt.figure()#←これの有無でエラー 11 print("endworler") 12 13root = tk.Tk() 14thread = Thread(target=worker, args=(root,)) 15 16root.after(1000*1, thread.start) # 1秒後に開始 17root.after(1000*3, root.quit) # 3秒後にGUIを終了 18root.mainloop()
回答2件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/10/17 23:56