質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
並列処理

複数の計算が同時に実行される手法

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Raspberry Pi

Raspberry Piは、ラズベリーパイ財団が開発した、名刺サイズのLinuxコンピュータです。 学校で基本的なコンピュータ科学の教育を促進することを意図しています。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

2442閲覧

Tkinterのthreading処理がうまく行えない

goripon1905

総合スコア1

並列処理

複数の計算が同時に実行される手法

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Raspberry Pi

Raspberry Piは、ラズベリーパイ財団が開発した、名刺サイズのLinuxコンピュータです。 学校で基本的なコンピュータ科学の教育を促進することを意図しています。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2021/08/20 12:35

実現したいこと

threadingを使用してdoorとmainの処理を行い、mainではtkinterに画像を表示し、doorではラズベリーパイに接続したスイッチがON(センサーがくっついた状態)であればOFFになるまで何もせず待機し、OFF(センサーが離れた状態)になったらtkinterのウィンドウを閉じるというプログラムを作りたい。

発生している問題・エラーメッセージ

RuntimeError: main thread is not in main loopやAttributeError: 'PhotoImage' object has no attribute '_PhotoImage__photo'%Run doortest.pyというエラーが発生し、door側のプログラムが上手く動作しない。

Exception in thread Thread-2: Traceback (most recent call last): File "/usr/lib/python3.7/threading.py", line 917, in _bootstrap_inner self.run() File "/usr/lib/python3.7/threading.py", line 865, in run self._target(*self._args, **self._kwargs) File "/home/pi/ securepy/doortest.py", line 37, in main img = ImageTk.PhotoImage(img) File "/usr/lib/python3/dist-packages/PIL/ImageTk.py", line 117, in __init__ self.__photo = tkinter.PhotoImage(**kw) File "/usr/lib/python3.7/tkinter/__init__.py", line 3545, in __init__ Image.__init__(self, 'photo', name, cnf, master, **kw) File "/usr/lib/python3.7/tkinter/__init__.py", line 3501, in __init__ self.tk.call(('image', 'create', imgtype, name,) + options) RuntimeError: main thread is not in main loop Exception ignored in: <function PhotoImage.__del__ at 0xb5e1ff18> Traceback (most recent call last): File "/usr/lib/python3/dist-packages/PIL/ImageTk.py", line 123, in __del__ name = self.__photo.name AttributeError: 'PhotoImage' object has no attribute '_PhotoImage__photo'%Run doortest.py

該当のソースコード

python

1import sys 2import threading 3import time 4import tkinter 5from subprocess import run 6from PIL import Image, ImageTk 7import RPi.GPIO as GPIO 8 9global root 10 11GPIO.setmode(GPIO.BCM) 12GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD_UP) 13sw_status = 1 14 15def door(): 16 root = tkinter.Tk() 17 while True: 18 try: 19 sw_status = GPIO.input(18) 20 if sw_status == 0: 21 pass 22 else: 23 break 24 25 time.sleep(2) 26 except: 27 break 28 GPIO.cleanup() 29 root.destroy() 30 sys.exit() 31 32def main(): 33 root = tkinter.Tk() 34 img = Image.open('img3.png') 35 img = ImageTk.PhotoImage(img) 36 canvas = tkinter.Canvas(bg="black", width=600, height=300) 37 canvas.place(x=0, y=0) 38 canvas.create_image(0, 0, image=img, anchor=tkinter.NW) 39 40 root.mainloop() 41 42if __name__ == '__door__': 43 root = tkinter.Tk() 44 door() 45 46if __name__ == "__main__": 47 thread_1 = threading.Thread(target=door) 48 thread_2 = threading.Thread(target=main) 49 50 thread_1.start() 51 thread_2.start()

試したこと

door側のプログラムにroot.mainloop()を追加することで動くかと思われたが、見かけ上は動いているがセンサーがOFF状態になってもウインドウが閉じられなかった。
何がいけないのか分からず完全にお手上げ状態となってしまったので、どなたかわかる方にどこに何を追加したらいいのか教えていただきたいです。

補足情報(FW/ツールのバージョンなど)

Python 3.7.3

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

teamikl

2021/08/22 09:40

> if __name__ == '__door__': はどのような実行を想定されてますか?
guest

回答1

0

ベストアンサー

問題点:

  • tkinter.Tk() は複数回呼び出さない。

 ライブラリの初期化が含まれるため、複数回呼び出すのは推奨しません。
ウィンドウが複数必要な場合は、tkinter.Toplevelを用います。

  • GUI のイベントループとdoorのループ処理の競合

 
解決策A: GUIのタイマー機能(tkinterではroot.after) を使い
定期的に GPIO からのデータを読み込む
解決策B: GUI(tkinter) と GPIO を別スレッドに別ける
GPIO からのデータを GUI に反映させるには、スレッド間通信や排他制御が必要になります。
→ GPIO を扱うスレッドでは、tkinter は直接操作しない。(マルチスレッドでの排他制御)


diff

1 img = Image.open('img3.png') 2- img = ImageTk.PhotoImage(img)

現行の tkinter は、PNGフォーマットに対応してるので、PIL.ImageTk は
過去のバージョンをサポートするのでない限りは不要です。

関連:

  • python tkinter で スライドショーを作りたいけどエラーでThreadが回せない

https://teratail.com/questions/194806

同じエラーでの報告事例。GUIのタイマー機能での実装を推奨されてます。

thread を使う実装方法もありますが、注意点が増え・コードが複雑になる為、
特別な理由がない限りは、タイマーでの実装が無難です。
(※ 障害時にGUIがフリーズするのを避けたい等、稀なケースへの対処)

投稿2021/08/22 10:09

編集2021/08/22 10:10
teamikl

総合スコア8664

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

goripon1905

2021/08/23 01:39

tkinterのウィンドウは1つで、mainで書いている画像を表示させておきたいです。 そしてdoor側の処理でもしOFFになった場合はmainで表示したウィンドウを閉じるというプログラムを作成したいです。 そのためウィンドウが複数必要ではないのでtkinter.Toplevelは使えません。 その場合、tkinter.Tk()を複数回呼び出さないためにはどうすればいいですか? また、マルチスレッドでの排他制御を行う場合は上記のコードはどのようになりますか? 排他制御はやったことがないのでわかりませんでした... PILに関しては不要とのことでしたので削除しました。 よろしくお願いします。
teamikl

2021/08/23 08:03 編集

> ウィンドウを閉じる この場合の実装は、ウィンドウを「非表示」にした方が良いです。 ## ひとつのウィンドウを再利用する場合 root.withdraw() # ウィンドウを非表示 root.iconify() # 非表示状態のウィンドウを表示 ## ウィンドウを毎回生成・破棄する場合 # tkinter.Tk は破棄前に複数回呼ばないように注意が必要 root = tkinter.Tk() # tkinterの初期化 + 内部でウィンドウ(Toplevel)生成 root.destroy() # ウィンドウを破棄 > マルチスレッドでの排他制御を行う場合は上記のコードはどのようになりますか? queue を使いスレッド間でデータをやり取りする方法が一般的です。 質問のコードではサブスレッドを2つ使ってますが、 GUIでスレッドを利用する場合、メインスレッドで GUI (tkinter)、 サブスレッドでは GPIO という風に分離します。 - 別スレッドからは、 queue にメッセージを入れます。(queue.put)  ※ 別スレッドから 直接GUI を操作するのは、安全な操作でありません。  質問のコードで言うと、door()関数内からは tkinter は一切参照しないようにします。 - メインスレッドからはタイマー(tkinter の after) で定期的に queue のデータを読み出す  注意点: GUIの処理を停滞させないように、  読み出しは、queue.get_nowait でブロッキングしないようにする。 - メインスレッド側で読み出したデータに応じたGUIの操作をする。 といった流れになります。 何れにしてもタイマー(tkinter の after) を使う事になるので、 スレッドでの実装の前に、まずはタイマーを使った実装をお勧めします。
goripon1905

2021/08/24 01:55

ご回答いただいたように、まずはタイマーでの実装をしてみようと試しに作ってみたのですが、画像が貼り付けられたtkinterのウィンドウは出てくるもののGPIOのスイッチがOFFの状態になってもウィンドウがdestroyされませんでした。 そもそも私の作り方が間違えている可能性がありますが、どこが間違っていますでしょうか...? ```python import time import tkinter import tkinter as tk import RPi.GPIO as GPIO GPIO.setmode(GPIO.BCM) GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD_UP) sw_status = 1 class Door(): def __init__(self): self.root = tkinter.Tk() host = tk.PhotoImage(file="testimg.png") canvas = tk.Canvas(bg="black", width=1024, height=600) canvas.place(x=0, y=0) canvas.create_image(0, 0, image=host, anchor=tk.NW) self.root.mainloop() def stop(self): self.root.destroy() def door(self): while True: try: sw_status = GPIO.input(18) if sw_status == 0: pass else: break time.sleep(2) except: break GPIO.cleanup() self.root.after(1000, self.root.destroy) app=Door() ```
teamikl

2021/08/24 03:11 編集

インデントが不明瞭なので問題を再現できません。 見た感じ door メソッドの呼び出しもありませんが、door実装にも問題があり 質問のコードをベースにする場合は大幅な変更が必要なので、 tkinterのタイマーを使ったサンプルコードを参考にし、 後々、定期的に実行する処理の部分を GPIO からの読み出しにするとよいです。 whie True: ~ time.sleep(2) は、GUIのスレッド内で基本は使えません。 (補足: 方法自体はありますが注意事項が増える為、お勧めは出来ません) GUIの処理が滞ってしまい、ウィンドウが応答なしになります。 ループの代替として、「〇秒間隔で定期的に処理を実行する」為にタイマーを用います。
teamikl

2021/08/24 03:05

もう一点、root.destroy はウィンドウが一つしかない場合、 タイマーを実行しているイベントループ (mainloop) も破棄されるので、 ウィンドウを閉じると GPIO の読み出しのタイマーは実行されなくなります。 この挙動が好ましくない場合は、destroyでの破棄ではなく 上述したウィンドウの表示・非表示を検討してください。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問