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

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

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

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

Tkinter

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

非同期処理

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

Q&A

解決済

1回答

5210閲覧

asyncioでtkinterのダイアログを開けない

namuyan

総合スコア76

Python 3.x

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

Tkinter

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

非同期処理

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

0グッド

0クリップ

投稿2019/07/06 16:36

編集2019/07/06 16:36

Python3.6を使用してasyncioとtkinterを用いたコードを書きたいと思います。
しかし残念ながらこの二つは相性が悪く併用が難しいです。

dialog.py

python3

1from tkinter import filedialog, Tk, Frame 2import platform 3from pathlib import Path 4 5 6class Dialogs(Frame): 7 def __init__(self): 8 self.root = Tk() 9 super().__init__(self.root) 10 self.root.geometry("0x0") 11 self.root.overrideredirect(1) 12 self.root.withdraw() 13 self.system = platform.system() 14 self.pack() 15 16 def open_dialog(self, dialog_open_method=filedialog.askopenfilename, options=None): 17 if self.system == "Windows": 18 self.root.deiconify() 19 self.root.update() 20 self.root.lift() 21 self.root.focus_force() 22 path_str = dialog_open_method(**(options or {})) 23 self.root.update() 24 if self.system == "Windows": 25 self.root.withdraw() 26 return Path(path_str)

ダイアログを開きファイルを選択します。

main.py

python3

1from dialog import Dialogs 2import asyncio 3 4loop = asyncio.get_event_loop() 5dialog = Dialogs() 6 7 8async def looper(): 9 while True: 10 await asyncio.sleep(1) 11 print("loop!") 12 13 14async def main(): 15 asyncio.ensure_future(looper()) 16 path = dialogs.open_dialog() 17 print(path) 18 19 20if __name__ == "__main__": 21 loop.run_until_complete(main())

上記のコードはEventLoopをブロックしてしまいます。
通常はrun_in_executorを用いるところですがRuntimeError: main thread is not in main loopとエラーを出されます。
どのようなコードを用いればEventLoopをブロックされずに済むのでしょうか?
回答の方を宜しくお願いします。

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

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

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

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

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

guest

回答1

0

ベストアンサー

適切な回答かどうか自信がない(asyncioの仕様経験が乏しい)のですが、自分は次のように考えました。

  • Tkinterはメインスレッドで動かす。

run_in_executorを使ってしまうとメインスレッド以外でTkinterが実行されることになるためか、ご質問にあるとおりうまくいかないようです。メインスレッド以外でTkinterのイベントループが動くものかどうかよく調べてはいないですが・・・。よってTkinterに関する処理をasyncioのタスクとして動かすのはあきらめざるを得ないように思います。async関数の中でTkinterを呼び出しても動くことは動くでしょうが、そこで非同期I/O処理は全て止まってしまうため並行して動かしたいならasyncioを動かすスレッドとは別のスレッドで(asyncioの枠組みとは別の世界で)動かす必要があるように思います。

  • 非同期I/Oはメインスレッド以外でも動くのでこちらをサブスレッドで動かすようにする。

  • asyncioの処理はコルーチンをディスパッチしないと進まない

mainにてタスクの実行を(awaitなどで)待ってないので、コルーチンは生成されるものの、その実行が先に進まないままmainが終わってしまいます。

  • Tkinterの処理をasyncio側で待つ

asyncio.Futureを作り、メインスレッド側でそのFutureにset_resultで結果を設定してやるとasyncio処理側でそのFutureを待つことで同期および結果が利用できると考えました。他にもっとよい(自然)な方法があるかも知れません・・・

main.py

python

1from dialog import Dialogs 2import asyncio 3import threading 4 5 6# 元のコードそのままにしたが、別スレッドで非同期I/Oを行うなら 7# サブスレッドで asyncio.new_event_loop() とした方が自然か? 8loop = asyncio.get_event_loop() 9dialog = Dialogs() 10 11 12async def looper(): 13 tname = threading.current_thread().name 14 print(f"{tname}: looper start") 15 for i in range(10): 16 await asyncio.sleep(1) 17 print(f"{tname}:{i}: awaken") 18 19 20class AsyncioThread(threading.Thread): 21 def __init__(self, loop, dialog_future, *args, **kwargs): 22 self.loop = loop 23 self.dialog_future = dialog_future 24 super().__init__(*args, **kwargs) 25 26 def run(self): 27 future = main(self.dialog_future) 28 print(f"{self.name}: thread start") 29 print(f"{self.name}: loop = {self.loop}") 30 print(f"{self.name}: future = {future}") 31 32 self.loop.run_until_complete(future) 33 print(f"{self.name}: completed") 34 35 36async def main(dialog_future): 37 await asyncio.gather(asyncio.ensure_future(looper()), dialog_future) 38 print(f"main: path={dialog_future.result()}") 39 40 41def main1(): 42 # loop = asyncio.new_event_loop() 43 dialog_future = asyncio.Future() 44 thread = AsyncioThread(loop=loop, dialog_future=dialog_future, daemon=False) 45 46 thread.start() 47 48 path = dialog.open_dialog() 49 print(path) 50 dialog_future.set_result(path) 51 52 thread.join() 53 54 55if __name__ == '__main__': 56 main1()

投稿2019/07/07 04:04

編集2019/07/07 04:08
KSwordOfHaste

総合スコア18392

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

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

namuyan

2019/07/07 14:33

回答して頂きありがとうございます。EventLoopはMainThreadでないと動きませんよ、のはずだったのですが動きますね。KILLSIGが理由でWindowsでは動かなかった記憶がありMainThreadである必要があると考えていました。いったい何と間違えていたのであろう?
KSwordOfHaste

2019/07/08 01:52

EventLoopはメインスレッド以外でも動くと思います。実際run_in_executorはサブスレッドで動作するわけですからEventLoopがメインスレッド以外でも動いてくれないと困ると思います。 ひょっとしたらEventLoopがmainスレッドでは最初から用意されているのに対してmainスレッド以外では用意されていない点(get_event_loop()はデフォルト状態ではmainスレッドに対してしか通用しないため、loopの省略を許す多くのasyncio関数がデフォルトでmainスレッド以外では動かない)と混同しておられたりしないでしょうか?
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問