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

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

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

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Q&A

解決済

2回答

1915閲覧

python asyncioを使用した際GUIが動かなくなる

TheBullchannel

総合スコア33

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

0グッド

1クリップ

投稿2023/01/24 07:29

編集2023/01/25 11:38

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

tkinterを使用してGUIを作成し、Websocket接続ボタンを押すことで、websocket通信を開始させたいのですが、
ボタンを押すと通信は開始されるが、その後、GUIが一切の入力を受け付けなくなってしまいます。
(画面がホワイトアウトし、くるくるが回っている状態)

下記のコードに問題がある点、対処方法を教えて頂きたいです。

よろしくお願いいたします。

class A: def __init__(self,master):    ~~省略   async def connect_ws(self): uri = 'ws://localhost:18080/xxxxxxx/websocket' async with websockets.connect(uri, ping_timeout=None) as ws: while not ws.closed: response = await ws.recv()   class Win2(tk.Frame):   def __init__(self,master):    self.A = A()    ~~省略   def create_widgets(self):     self.makeorder_button_connectws = ttk.Button(self.labelFrame, text='Websocket接続',command = self.connect_ws) self.makeorder_button_connectws.pack()   ~~省略   def connect_ws(self):     loop = asyncio.get_event_loop() loop.create_task(self.A.connect_ws()) try: loop.run_forever() except KeyboardInterrupt: exit()

使用しているバージョン

python 3.10

tkinter 8.6
asyncio-3.4.3

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

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

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

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

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

Demerara

2023/01/24 15:40

tkinter についてはよく知りませんが、async / await を関数の中で使うのであれば、その関数そのものも async で定義する必要があります。 async def connect_ws(self): としてみてください。
TheBullchannel

2023/01/25 07:03

ありがとうございます。 tkinterのボタンを押すことで、class Win2 のconnect_wsを呼び出し、 async で定義したclass A のconnect_wsを呼び出し。webscoketの接続を試みましたが、 やはり、GUI(Win2)フリーズしてしまいます。 フリーズしてしまうのは、win2のconnect_wsメソッドが動き続けているからでしょうか.
Demerara

2023/01/25 08:25 編集

少し tkinter について調べてみましたが、async の使われ方が、python や tkinter のバージョン、使用しているモジュール等によってかなり違いがあるようです。差し支えなければ、import している tkinter 及び 非同期に関連するモジュールと python 等のバージョンも補足として質問本文に追記すると回答が得られやすくなるかもしれません。
TheBullchannel

2023/01/25 11:52

使用しているモジュールのバージョンを追記させていただきました. よろしくお願いいたします。
TheBullchannel

2023/01/25 15:37

試してみましたが、やはりwebsocket通信自体は開始されるものの、GUIが操作不能になってしまいます。 tkinterのmainloopの中ではasyncioのloopを動かせないのでしょうか。tkinterとは別のスレッドで動かす等はできるのでしょうか?
Demerara

2023/01/26 08:44

役に立ちそうな過去の質問を探してみました。参考にしてみてください。どうやら、tkinter の mainloop の中では asyncio を動かすのは無理っぽいので、thread とかを使うみたいです。 Python3 Tkinter Tkinterとソケット通信のサーバの立ち上げ https://teratail.com/questions/317832 Python3 非同期処理について https://teratail.com/questions/331789 asyncioでtkinterのダイアログを開けない https://teratail.com/questions/199068 Tkinter内でのループ処理 https://teratail.com/questions/299389
TheBullchannel

2023/01/29 14:33

Demerara様、ありがとうございました。 threadを用いて、別のスレッドにてasyncioのループを作成することにより、GUIが停止することなくwebsocket接続を行うことができました。
Demerara

2023/01/30 11:40

役に立てたようでよかったです。そのコメントの内容を回答欄に書いて自己解決として、この質問を閉じておいてください。
guest

回答2

0

解決済みのようですが、シングルスレッドで tkinter と asyncio を動かす方法について

tkinter と asyncio は、どちらもイベント駆動なので、
片方のイベントループが実行されている間は、他方のイベントループは待機中になります。

root.mainloop() や、loop.run_forever() は、
ブロッキング処理なのでコードの実行がウィンドウを閉じるまで止まってしまいます。
イベントループを実行するコードなので、他方のイベントループ内では使ってはいけません。

別スレッドにする事は、複数のイベントループを同時に実行する解決策の一つですが、
マルチスレッドでは、スレッド間での安全なデータの受け渡し方法が必要となってきます。
(マルチスレッドの場合は、同期キューを用いたスレッド間通信を調べてみてください)


シングルスレッドでの解消法は、
どちらかのイベントループ内で他方のイベント処理を定期的に更新する方法です。

プログラム全体は asyncio のイベントループで動かし、
tkinter のイベントループは、mainloop() は呼び出さずに、
asyncio内で代替のイベントループを組みます。

python

1async def tk_event_loop(root, interval=1/120): 2 while True: 3 root.update() 4 await asyncio.sleep(interval)

投稿2023/04/02 00:09

teamikl

総合スコア8664

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

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

0

自己解決

class A:
def init(self,master):
~~省略
async def connect_ws(self):
async with websockets.connect('ws://localhost:18080/XXXXXXXXX/websocket', ping_timeout=None) as ws:
while not ws.closed:
response = await ws.recv()
board = json.loads(response)

def createloop(self,loop):
loop.create_task(self.connect_ws())
loop.run_forever()

class Win2(tk.Frame):
def init(self,master):
self.A = A()
~~省略

  def create_widgets(self):
self.makeorder_button_connectws = ttk.Button(self.labelFrame, text='Websocket接続',command = self.connect_ws)
self.makeorder_button_connectws.pack()
~~省略

  def connect_ws(self):
loop = asyncio.new_event_loop()
thread1 = threading.Thread(target=A.createloop, args=(loop))
thread1.start()

上記のように、あらかじめWin2クラス(メインのクラス)にてasyncioのループを作成しておきます。
次に、asyncioを動かしたいクラス内でloopにタスクを設定し、threading.threadに、loopとともに渡すことにより、
GUIのloopを妨げることなく、別スレッドにてasyncioを動作させることが可能です。

投稿2023/02/01 06:58

TheBullchannel

総合スコア33

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問