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

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

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

Discordは、ゲーマー向けのボイスチャットアプリです。チャット・通話がブラウザ上で利用可能で、個人専用サーバーも開設できます。通話中でも音楽を流したり、PC画面を共有できるなど多機能な点が特徴です。

Tkinter

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

Python

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

Q&A

解決済

3回答

3744閲覧

PythonでTkinterを使ってDiscordのRichPresenceを使えるようにしたい

yuki0369

総合スコア14

Discord

Discordは、ゲーマー向けのボイスチャットアプリです。チャット・通話がブラウザ上で利用可能で、個人専用サーバーも開設できます。通話中でも音楽を流したり、PC画面を共有できるなど多機能な点が特徴です。

Tkinter

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

Python

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

0グッド

0クリップ

投稿2020/05/14 12:02

編集2020/05/14 13:29

前提・実現したいこと

Python初心者です。
PythonでTkinterを使用し、ボタンを押すとDiscordのRichPresenceを用いてステータスにPC情報等を表示するプログラムを作ろうと思い最初にthreadingを使わずにプログラムを書いたのですが、ボタンを押し、有効化するとフリーズして強制終了してしまします。そこでthreadingを使うとフリーズしなくなると聞いたのでthreadingを使用しプログラムを書いてみたのですが、エラーが発生してしまいます。

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

C:\Users\ユーザー名\PycharmProjects\test\venv\Scripts\python.exe C:/Users/ユーザー名/PycharmProjects/test/python2.py Exception in thread Thread-1: Traceback (most recent call last): File "C:\Users\ユーザー名\AppData\Local\Programs\Python\Python36-32\lib\threading.py", line 916, in _bootstrap_inner self.run() File "C:\Users\ユーザー名\AppData\Local\Programs\Python\Python36-32\lib\threading.py", line 864, in run self._target(*self._args, **self._kwargs) File "C:/Users/ユーザー名/PycharmProjects/test/python2.py", line 17, in rp RPC = Presence(client_id, pipe=0) File "C:\Users\ユーザー名\PycharmProjects\test\venv\lib\site-packages\pypresence\presence.py", line 13, in __init__ super().__init__(*args, **kwargs) File "C:\Users\ユーザー名\PycharmProjects\test\venv\lib\site-packages\pypresence\baseclient.py", line 37, in __init__ self.update_event_loop(self.get_event_loop()) File "C:\Users\ユーザー名\PycharmProjects\test\venv\lib\site-packages\pypresence\baseclient.py", line 80, in get_event_loop loop = asyncio.get_event_loop() File "C:\Users\ユーザー名\AppData\Local\Programs\Python\Python36-32\lib\asyncio\events.py", line 694, in get_event_loop return get_event_loop_policy().get_event_loop() File "C:\Users\ユーザー名\AppData\Local\Programs\Python\Python36-32\lib\asyncio\events.py", line 602, in get_event_loop% threading.current_thread().name) RuntimeError: There is no current event loop in thread 'Thread-1'.

該当のソースコード

Python

1import psutil 2from pypresence import Presence 3import time 4import platform 5import tkinter as tk 6from tkinter import messagebox as mbox 7import threading 8 9root = tk.Tk() 10root.geometry("500x300") 11 12client_id = 'クライアントID' 13 14n = "\n" 15 16starttime = time.time() 17 18def rp(): 19 RPC = Presence(client_id, pipe=0) 20 RPC.connect() 21 mbox.showinfo("info", "ステータス表示を有効化しました") 22 while True: 23 cpu_per = round(psutil.cpu_percent(), 1) 24 mem_per = round(psutil.virtual_memory().percent, 1) 25 RPC.update(start=int(starttime), details="メモリ使用率: " + str(mem_per) + "%", state="CPU使用率:"+str(cpu_per)) 26 time.sleep(1) 27 28 29def callback(): 30 th = threading.Thread(target=rp) 31 th.start() 32 33 34 35button1 = tk.Button(text="有効化", font=("", 30), width=15, height=5, command=callback) 36button1.pack() 37 38root.mainloop() 39

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

Windows10 64bit
Python3.6
RichPresenceの表示にはpypresenceを使用してます。

###追記
threadingからmultiprocessingに変えたところ、起動しボタンを押してrichpresenceを有効化するところまではできたのですが、そこで新しいウインドウが開きそれがフリーズしてしまいます。
最初に開いたウインドウはフリーズしていません。
https://stackoverflow.com/questions/46674498/tkinter-is-opening-multiple-gui-windows-upon-file-selection-with-multiprocessing
調べてみたところ上記のような情報が見つかり試してみたのですが、特に変わりはありませんでした。
以下変更後のソースコードです。

Python

1import psutil 2from pypresence import Presence 3import time 4import tkinter as tk 5from tkinter import messagebox as mbox 6import multiprocessing 7 8 9client_id = 'クライアントID' 10 11n = "\n" 12 13starttime = time.time() 14 15def rp(): 16 RPC = Presence(client_id, pipe=0) 17 RPC.connect() 18 mbox.showinfo("info", "ステータス表示を有効化しました") 19 while True: 20 cpu_per = round(psutil.cpu_percent(), 1) 21 mem_per = round(psutil.virtual_memory().percent, 1) 22 RPC.update(start=int(starttime), details="メモリ使用率: " + str(mem_per) + "%", state="CPU使用率:"+str(cpu_per)) 23 time.sleep(1) 24 25 26def callback(): 27 mp = multiprocessing.Process(target=rp) 28 mp.start() 29 30 31if __name__ == "__main__": 32 root = tk.Tk() 33 root.geometry("500x300") 34 35 button1 = tk.Button(text="有効化", font=("", 30), width=15, height=5, command=callback) 36 button1.pack() 37 38 root.mainloop()

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

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

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

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

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

guest

回答3

0

threadingを使わない方法になってしまいますが、
while Trueの中にウインドウを更新するroot.update()を挟めばフリーズしません。

Python

1import psutil 2from pypresence import Presence 3import time 4import platform 5import tkinter as tk 6from tkinter import messagebox as mbox 7import threading 8 9root = tk.Tk() 10root.geometry("500x300") 11 12client_id = 'クライアントID' 13 14n = "\n" 15 16starttime = time.time() 17 18def rp(): 19 RPC = Presence(client_id, pipe=0) 20 RPC.connect() 21 mbox.showinfo("info", "ステータス表示を有効化しました") 22 while True: 23 cpu_per = round(psutil.cpu_percent(), 1) 24 mem_per = round(psutil.virtual_memory().percent, 1) 25 RPC.update(start=int(starttime), details="メモリ使用率: " + str(mem_per) + "%", state="CPU使用率:"+str(cpu_per)) 26 time.sleep(1) 27 root.update() 28 29button1 = tk.Button(text="有効化", font=("", 30), width=15, height=5, command=rp) 30button1.pack() 31 32root.mainloop()

root.after()を使う方法もあります。個人的にはこっちのほうが好きです

Python

1import psutil 2from pypresence import Presence 3import time 4import platform 5import tkinter as tk 6from tkinter import messagebox as mbox 7import threading 8 9root = tk.Tk() 10root.geometry("500x300") 11 12client_id = 'クライアントID' 13 14n = "\n" 15 16starttime = time.time() 17 18def rp(): 19 cpu_per = round(psutil.cpu_percent(), 1) 20 mem_per = round(psutil.virtual_memory().percent, 1) 21 RPC.update(start=int(starttime), details="メモリ使用率: " + str(mem_per) + "%",state="CPU使用率:"+str(cpu_per)) 22 root.after(1000, rp) #1000ミリ秒後にrpを実行 23 24def start(): 25 global RPC 26 RPC = Presence(client_id, pipe=0) 27 RPC.connect() 28 mbox.showinfo("info", "ステータス表示を有効化しました") 29 rp() 30 31button1 = tk.Button(text="有効化", font=("", 30), width=15, height=5, command=start) 32button1.pack() 33 34root.mainloop()

投稿2020/05/14 12:27

編集2020/05/14 12:55
dragoemon

総合スコア19

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

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

yuki0369

2020/05/14 12:51

回答ありがとうございます。 どちらを使用してもフリーズしなくなりました。 ただ後者の方は def start(): global RPC としないと関数rpのRPC.updateが動かなくなってしまうようです。
dragoemon

2020/05/14 12:54

そうですね。失礼しました。修正しておきます。
yuki0369

2020/05/14 12:58

ありがとうございます。
teamikl

2020/05/14 12:59

上のコードは、while ループから抜けておらず、mainloopに処理が戻ってないので、 デバッグの際にエラーログが見にくくなるリスクがあります。(例えば、停止したい場合困ることに) 起動だけなら不要かもしれませんが、他のイベントも1秒毎にしか反応しなくなります。 after で済む場合はをそちらを使った方が良いです +1
yuki0369

2020/05/14 13:33

なるほど。 参考になります。 ありがとうございます。
guest

0

ベストアンサー

tkinterの利用をスレッドから分離したサンプルです。
有効化/無効化と、接続を保ったままの一時停止/再開を実装しました。

pypresence 利用の部分は試せなかった為、コメントに記述。
asyncio.new_event_loop()での修正を確認してから試してみてください。

拡張予定がなく、有効化のみのプログラムなら殆ど恩恵はないかもしれませんが、
スレッドを使う利点は、RPC.update の時にウィンドウが固まるリスクを下げられます。

後、訂正で「ネットワーク障害等により」と言っていた部分は、
「クライアントが応答をしない場合」が適切でした。(パイプであればネットワークは影響しない)

python

1import tkinter as tk 2from tkinter.messagebox import showinfo 3import time 4import itertools 5from types import SimpleNamespace 6from queue import Queue, Empty 7from threading import Thread, Event 8 9def rp_thread_run(queue, flag): 10 # event は必須ではありませんが、ダイアログ表示中に処理を停止したい場合 11 # この様にする必要があります。teek というライブラリを使うと、綺麗に書けます。 12 event = Event() 13 if not flag.wait_dialog.get(): 14 event.set() # <-- 待機しない場合、予め event.set を呼んでおく 15 queue.put(("showinfo", event, "info", "start RP")) 16 event.wait() # <-- event.set が呼ばれる迄待機 17 18 ## これは 厳密にはスレッドセーフではないが、これで充分事足りる事が多いが、 19 ## スレッド内からtkinterへ頻繁に何か表示したいといった場合は、問題が顕在化することがあります。 20 # showinfo("info", "start RP") 21 # root.after_idle(showinfo, args=("info", "start RP")) 22 23 print("RPC opened") 24 queue.put(("status", None, "connected")) 25 counter = itertools.count() 26 while flag.running: 27 flag.suspend.wait() 28 print(next(counter)) # 動作確認用に、数値を出力 29 time.sleep(1) 30 print("RPC closed") 31 queue.put(("status", None, "disconnected")) 32 33 ## thread-safe でないケースの実演。以下のコードは 34 ## スレッド稼働中にウィンドウを閉じるとエラーになります。 35 # status.set("AAA") # RuntimeError 36 37 38def main(): 39 # 本来は不要なグローバル宣言しませんが、スレッド内でのテスト用。(不要になったら消してください) 40 global status 41 global root 42 root = tk.Tk() 43 queue = Queue() 44 flag = SimpleNamespace(suspend=Event(), running=True, wait_dialog=tk.BooleanVar(value=True)) 45 46 def rp_thread_start(event=None): 47 Thread(target=rp_thread_run, args=(queue, flag)).start() 48 49 def rp_button_start(): 50 flag.running = True 51 flag.suspend.set() 52 button1.config(text="Stop", command=rp_button_stop) 53 button2.config(state="normal") 54 rp_thread_start() 55 56 def rp_button_stop(): 57 flag.running = False 58 flag.suspend.set() 59 button1.config(text="Start", command=rp_button_start) 60 button2.config(state="disable") 61 62 def rp_button_pause(): 63 flag.suspend.clear() 64 button2.config(text="Resume", command=rp_button_resume) 65 66 def rp_button_resume(): 67 flag.suspend.set() 68 button2.config(text="Pause", command=rp_button_pause) 69 70 def on_delete_window(): 71 flag.running = False 72 flag.suspend.set() 73 root.destroy() 74 75 # Thread-safe にする為、メインスレッド上でキューを処理 76 def process_queue(): 77 # ※ time.sleep 等のメインループの実装を阻害するようなコードは避ける。 78 try: 79 item = queue.get(block=False) # <-- スレッドから送られたメッセージを受け取る 80 name, event, *args = item 81 if name == "showinfo": 82 showinfo(*args) 83 event.set() # <-- ダイアログで待機中の状態を解除 84 elif name == "status": 85 status.set(args[0]) 86 except Empty: 87 pass 88 root.after(100, process_queue) # <-- 1個/100ms キューアイテムの処理速度(数値は要調整) 89 process_queue() 90 91 button1 = tk.Button(root, font=("", 30), width=15, height=5) 92 button1.pack() 93 button2 = tk.Button(root, font=("", 30), width=15, height=5) 94 button2.pack() 95 frame = tk.Frame(root) 96 frame.pack(fill=tk.BOTH) 97 tk.Checkbutton(frame, text="ダイアログが閉じられるのを待ってから開始", variable=flag.wait_dialog).pack(side=tk.LEFT) 98 tk.Button(frame, text="Quit", command=on_delete_window).pack(fill=tk.BOTH, side=tk.RIGHT) 99 status = tk.StringVar(root) 100 tk.Label(root, textvar=status).pack(fill=tk.BOTH) 101 102 rp_button_stop() 103 rp_button_resume() 104 105 root.protocol("WM_DELETE_WINDOW", on_delete_window) 106 root.mainloop() 107 108 109if __name__ == '__main__': 110 main()

イメージ説明

デモ内容:

  • 「Start」 開始
  • ダイアログが閉じてから、スレッド内のカウント開始
  • 「Pause」「Resume」一時停止と再開
  • 「Stop」停止時に
  • 「Start」再スタート
  • ダイアログが開いたまま、スレッド内のカウント開始
  • 「Quit」ウィンドウを閉じた時にも RPC の close が呼ばれるのを保証。

投稿2020/05/14 21:46

編集2020/05/15 06:37
teamikl

総合スコア8664

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

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

yuki0369

2020/05/15 00:51

書いて頂いたサンプルコードでpypresenceを使って書いてみたところ、正常に動作させることができました。 とても勉強になりました。 回答ありがとうございました。
guest

0

Presenceを使ったことはありませんが、エラーログにasyncioとあったので、これでどうでしょう。

python

1import asyncio 2 3def rp(): 4 RPC = Presence(client_id, pipe=0, loop=asyncio.new_event_loop())

asynciomainスレッド以外で使う場合は、明示的にイベントループを指定する必要があり、
大抵は、loop引数を渡せる設計になっています。

もしくは、スレッドではなくプロセスにすると期待通りに動作するかもしれません。

投稿2020/05/14 12:55

編集2020/05/14 14:59
teamikl

総合スコア8664

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

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

yuki0369

2020/05/14 13:31

回答ありがとうございます。 asyncioでは同じエラーが出たのですが、プロセスにしたところ、GUIを開きボタンを押しrichpresenceを有効化させるところまではできました。 しかし、その後もう一つ何も書いていない別のウインドウが開き、それがフリーズしてしまいます。 最初に起動したウインドウはフリーズしていません。 調べてみたところ https://stackoverflow.com/questions/46674498/tkinter-is-opening-multiple-gui-windows-upon-file-selection-with-multiprocessing このような記事が見つかったため試してみたのですが、特に変わりはありませんでした。 変更後のソースコードを追記しておいたので、何か解決法をご教授いただければ幸いです。
teamikl

2020/05/14 14:04

フリーズの原因は、while True と time.sleepですが、 rp()関数内でもダイアログ表示にtkinterを使ってたのですね。見落としてました。 回避する為には、showinfo をプロセス開始前に呼ぶか、 ウィンドウを消してイベントループ内でupdate() を呼ばなければいけないが 上述したデメリットがあります。(mainloopから呼ばれていないという違いはありますが) # if __name__ == "__main__" の前に root = tk.Tk() root.withdraw() # ダイアログ表示時に表示されるウィンドウを消す # while ループ内でウィンドウをフリーズさせない為に root.update() 別プロセス側でも2重にtkinterが使われてしまう為、 dragoemonさんのroot.after を使った方法の方が、 データのやり取り等が必要になった場合も、 同じスレッド内で行う方がやりやすいので、そちらをお勧めします。 懸念は、ネットワーク障害等によりRPC.update で時間が掛かった場合に、GUIが固まってしまう点 今の所は起動のみの様ですが、他に機能拡張の予定はありますか? データやり取りが発生したりする場合は、一時的な回避策よりも、 プロセス側からtkinter を完全に分離する方が良いです。 ---- discord bot のトークンみたいなのは取ってるのですけど、Presenceは使ったことなかったので 環境整備出来たら tkinter を完全に分離するサンプル書いてみます。
yuki0369

2020/05/14 14:23

返信ありがとうございます。 ご教授頂いた方法を試したところ、正常に動作させることができました。 ですが当面はroot.afterを使って書こうと思います。 今のところは特に拡張する予定はないです。 サンプルの件、ありがとうございます。
teamikl

2020/05/14 15:31

asyncio 対応のコード修正しました。 get_event_loop() ではなく、正しくは new_event_loop() でした。 問題のエラーは回避出来ましたが、実行環境が整ってなかったため他のエラーで断念。 クライアントとパイプで通信する仕組みだったんですね。botと勘違いしてました。
yuki0369

2020/05/15 00:51

そうですね、自分が作ろうとしていたのはbotではなく自分のステータスにRichPresenceを表示するプログラムですね。 そして修正して頂いたasyncioですがスレッドを使用した状態で無事動作しました。 ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問