質問: Thread(target=worker, args=(root,)) root.after(1000*1, thread.start) # 1秒後に開始 root.after(1000*3, root.quit) # 3秒後にGUIを終了 root.mainloop() ```
回答: > > サブスレッドでGUIを動かすべきでないというのはTkinterの話だと思っていたのですが、Matplotlibも実行すべきでないのでしょうか? > > > Matplotlib のバックエンドに依存します。 バックエンドが Tkinter の場合は、plot にも Tkinter が使われます。 > > できるだけ早いグラフ更新(2ms更新を目指していたが受信都度更新だと10msが限界だなあと感じていたところ)を目指していたのであまり処理が長くなるのは避けたかったのですが、サブスレッドはデータの受信のみとし、メインに受信データを渡してグラフ描画としなければならないということでしょうか? > > > 2ms での更新は、FPS 500 (2ms毎更新 は 1秒間に 500回更新) なので、 殆どの場合は、そこまで細かく更新する必要はないはずです。 目安: 1000ms / 60fps(60Hz) = 16.666.. ms なので 受信はサブスレッドで 2ms 間隔で行うとしても、 メインスレッドでtkinterのタイマー機能を使い 描画反映は、10~15ms間隔で更新すると良いです。 > > メインスレッドがWhileループ内にいるにもかかわらず、GUIの操作ができ、ボタンも反応しました。(おそらく)self.fig.canvas.flush_events()がループに存在しているのが原因だと思われるのですが、なぜGUIの操作ができるのでしょうか > > > ここも、Matplotlib のバックエンド次第では挙動が変わる部分ですが、 バックエンドもTkinterの場合は、flush_events により Tkinter の GUI操作等のイベントが処理されます。 バックエンドが異なる場合は、Tkinter の GUI はフリーズするので、 その場合は、root.update() を定期的に呼び出す必要があります。 | | イベントループ | イベント処理 | ループ終了 | | --- | --- | --- | --- | | tkinter | mainloop | update/update_idletasks | quit | | FigureCanvas | start_event_loop | flush_events | stop_event_loop | matplotlib の FigureCanvas では、バックエンドのGUIの対応する処理が、 内部で行われてると考えると理解しやすいと思います。 tkinter のバックエンド matplotlib/backends/_backend_tk.py flush_events Canvas ウィジェトの update() ですが、 tkinter は全てのウィジェットからアクセスできるようになっているだけなので、 rootとから呼び出す場合と同じものです。 バックエンドが Tkinter の場合は flush_events 内部で update() が呼ばれてます。 質問の最初のコードの メインスレッドで mainloop サブスレッドで 独自のループを組み flush_events という構成について、 任意のタイミングでGUIを更新するのには、flush_events() は有効ですが、 (バックエンドがtkinterの場合は) mainloop との併用は好ましくありません。 mainloop を使う場合は flush_events は極力使わないようにすべきです。 稀にしか起こらないような、再現性の低い不具合を引き起こし、 デバッグが難しくなりやすい傾向があります。 flush_events を使う場合は、独自にイベントループを組みたい場合、 複数のイベントループを組み合わせて使いたい場合等、限られた用途になります。 例えば、 * Matplotlib のバックエンドがTkinter以外の場合 * asyncioを一緒に単一スレッド上で使いたい場合、等 今回の場合は、メインスレッドで tkinter のmainloop サブスレッドで独自に組んだイベントループですが、 バックエンドが同じ tkitner の場合は、イベントループの競合となってしまいます。