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

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

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

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

Q&A

解決済

2回答

5532閲覧

PySimpleGUIを使い、定期実行する他プログラムを実行する方法について

Rodeo.9071

総合スコア4

Python 3.x

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

1グッド

1クリップ

投稿2021/05/14 18:13

PYSimpleGUIにて作成したボタンを押すと、(決まった時刻に実行される)他プログラムを呼び出して実行するというアプリを作成したいのですが、ボタンを押すと"応答なし"というコメントが表示されたまま、GUIの画面がフリーズしてしまいます。

例として、毎朝8:00に「おはよう」と出力されるアプリをPYSimpleGUIで作成する場合、以下のプログラムをどのように修正すれば宜しいでしょうか。

※ なお、2つのプログラムの関係性は変更しないままで修正する方法ありますでしょうか。
また、呼び出される側はスケジュールされたプログラムとしたいです。

<PYSimpleGUI側(呼び出す側のモジュール)のコード>
import PySimpleGUI as sg
import main

layout = [
[sg.Text('実行ボタンを押してください')],
[sg.Submit(button_text='実行', size=(20,1))],
]

window = sg.Window('おはようアプリ', layout)

while True:
event, values = window.read()

if event is None: print('exit') break if event == '実行': main.sub()

window.close()

<呼び出される側のプログラム(main.py)>
import schedule
import time

def sub():
def job():
print("おはよう!")

schedule.every().day.at("08:00").do(job) while True: schedule.run_pending() time.sleep(60)

Pythonを独学で勉強したばかりで、質問の内容もうまく伝えらていないかもしれませんが、
どうにもならず、大変困っております。
よろしくお願いします。
(なお、利用環境はWindows10proでツールはPython3.9になります)

teamikl👍を押しています

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

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

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

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

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

guest

回答2

0

何度もありがとうございました!
下記のコードでなんとかうまくいきました。
本当にありがとうございます。

python

1###<呼び出し元のプログラム>### 2 3import PySimpleGUI as sg 4import main5 5import threading 6import time 7 8layout = [ 9 [sg.Text('実行ボタンを押してください')], 10 [sg.Submit(button_text='実行', size=(20,1))], 11] 12 13# ウィンドウの生成 14window = sg.Window('おはようアプリ', layout) 15 16thread=main5.sub 17 18# イベントループ 19while True: 20 event, values = window.read() 21 22 if event is None: 23 print('exit') 24 break 25 26 elif event==('実行'): 27 #main5.sub() 28 threading.Thread(target=main5.sub, daemon=True).start() 29 print("ん") 30 31# ウィンドウの破棄と終了 32window.close()

python

1import schedule 2import time 3 4def sub(): 5 def job(): 6 print("おはよう!") 7 8 schedule.every().day.at("05:10").do(job) 9 10 while True: 11 schedule.run_pending() 12 time.sleep(5)

投稿2021/05/18 20:18

Rodeo.9071

総合スコア4

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

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

teamikl

2021/05/19 04:17 編集

thread=main5.sub の部分は、このコードでは無くても影響ありません。 「2重起動防止」が、文章で説明だったので解りにくかったかもしれないですね。 # thread=main5.sub の位置で、変数 thread に None を入れておく thread=None # イベントループ while True:  # (略)  elif event==('実行'): # <-- ここの括弧 () は不要です。   if not thread: # 「実行」が未だ押されてない場合    thread = threading.Thread(target=main5.sub, daemon=True)    thread.start()    # 2回目以降は、thread は空ではないので、複数回実行されない。 ※ コメント内では空白が見えない為、インデントは全角スペースを利用してます。 実際のコードでは適切にインデントしてください。
Rodeo.9071

2021/05/19 14:31

コメントありがとうございます。 ちなみにですが、 >2回目以降は、thread は空ではないので、複数回実行されない。 につきまして、2回目以降も実行されるようにするにはどのように対処すれば 宜しいでしょうか。 何度もすみませんが、よろしくお願いします。
teamikl

2021/05/19 14:44

>2回目以降も実行されるように 確認ですが、2回目というのはスレッドですか?それともスケジュールしたタスクがですか? スケジューラーを動かすスレッドは通常一回起動すればいいので、2つ起動しても意味がありません。 どのような使い方を想定してるのでしょう?スケジューラを一旦止めてもう一度起動したい等ですか?
Rodeo.9071

2021/05/19 15:07 編集

2回目以降というのはスケジュールしたタスクのことです。 このアプリは定刻に"おはよう"というコメントを出力するものですが、 1回目の"おはよう"を出力したあと、ちゃんと翌日も出力されるのかを心配しておりました。
teamikl

2021/05/19 15:27 編集

スケジュールしたタスクであれば大丈夫です。問題ありません。 一応、説明しておくと、 現状のコードの問題点は、「実行」ボタンを複数回押すと その回数だけスレッドが作られてしまいます。 スケジュールされたタスクが重複して実行される事はありませんが、 明らかに無駄になるので、2回目以降を~というのは「実行」ボタンを複数回押したときに スレッドが作成済みの場合は、複数のスレッドを作らないようにする為の措置です。
Rodeo.9071

2021/05/19 15:37

そう意味だったんですね。ちゃんと理解しておらず、申し訳ありません。 また、ご丁寧に何度もご対応いただき、心より感謝しております。 本当にありがとうございます!!
guest

0

ベストアンサー

問題点: イベントループ内での無限ループ

python

1while True: 2 event, values = window.read()

PySimpleGUI では、この window.read() 内部で
GUIの描画やマウス・キーボード操作等の処理を行い、
何かイベントがあった時に、event, values を返します。

現状のコードでは、main.sub() 内で更にループがある為、
window.read() 迄処理が戻らず、ウィンドウが応答なしになります。

python

1while True: # イベントループ 2 event, values = window.read() 3 4 if event == '実行': 5 # main.sub() をここに展開して書くと 6 7 while True: # ここで無限ループが実行され続ける為、 8 ... # window.read() に処理が戻らない。

解決策 A: スレッドを使う

PySimpleGUI のデモにマルチスレッドを使うサンプルがあるので、
参考にしてみてはどうでしょう。

但し、スケジューラという事で、スレッドを使う位置等は工夫した方が良いです。
例えば、「実行」ボタンを複数回押した時の挙動等には注意してください。


解決策 B: タイマーを使う

  • window.read(timeout=60*1000) で定期的にイベントを発生できます。
  • イベントループ内で、schedule.run_pending() を呼び出します。

 time.sleep は不要で、timeout引数に実行する間隔をミリ秒で設定します。

  • この場合は、時間の掛かる処理は実行してはいけない事に注意をして下さい。

 時間の掛かる処理を行いたい場合は、スレッドを使うことになります。

PySimpleGUI のデモ タイマーを使うサンプル

投稿2021/05/14 23:54

編集2021/05/15 00:00
teamikl

総合スコア8664

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

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

Rodeo.9071

2021/05/15 05:16

ご丁寧な説明ありがとうございます。 解決策Aにつきまして、マルチスレッドで対応したいのですが、具体的にどのように直せばよいのでしょうか。下記のようにしてみましたが、現状改善されませんでした。 初学者であり、大変申し訳ありませんが、よろしくお願いします。 import PySimpleGUI as sg import main5 import threading import time layout = [ [sg.Text('実行ボタンを押してください')], [sg.Submit(button_text='実行', size=(20,1))], ] # ウィンドウの生成 window = sg.Window('おはようアプリ', layout) # イベントループ while True: event, values = window.read(timeout=10) if event is None: print('exit') break elif event==('実行'): #main.sub() threading.Thread(target=main5.sub(),daemon=True).start() # ウィンドウの破棄と終了 window.close()
teamikl

2021/05/19 00:56 編集

Thread の target には関数を指定するのですが、 target=main5.sub() では、その時点で関数を実行してしまっています。 Thread(target=main5.sub, daemon=True) ですね。 2重実行を防ぐためには、threadを変数に入れておくように while True: ル-プ外で thread = None 変数を準備しておき 実行イベント時に、事前確認します。 if not thread:  thread = Thread(...)  thread.start() ---- 尚、マルチスレッドにする場合は、read(timeout=10) は不要です。 タイマーにする場合は、 while True:  event, values = window.read(timeout=1000) # 1秒間隔  schedule.run_pending()  ... 別モジュールに分割する場合は、main5.sub のままでは扱えないので、 スケジューラへの job の登録と、 スケジューラの可動 schedule.run_pending 呼出 を、別々の関数で提供する必要があります。
teamikl

2021/05/15 07:12

説明補足です。マルチスレッドにした場合でも、 指定時間のタスク内(print文の部分)で、時間の掛かる処理を行う場合は、 更に別スレッドを作る必要がある点は注意してください。 GUIイベントループ内での時間の掛かる処理 → GUIのフリーズですが、 スケジューラのループ内での時間の掛かる処理は、 スケジュールの遅延になってしまいます。
Rodeo.9071

2021/05/17 18:27

何度も大変申し訳ありません。ご教授頂いた内容について、いくつか試したのですが、まだうまくいっておりません。 おそらく、「thread = None 変数を準備しておく」というご指摘がうまく取り込めていないかと思うのですが、下記からどのように修正すれば宜しいでしょうか。 <呼び出し元のプログラム> import PySimpleGUI as sg import main5 import threading import time layout = [ [sg.Text('実行ボタンを押してください')], [sg.Submit(button_text='実行', size=(20,1))], ] # ウィンドウの生成 window = sg.Window('おはようアプリ', layout) #2重実行の防止 if thread:  thread = Thread(...)  thread.start() # イベントループ while True: event, values = window.read() if event is None: print('exit') break elif event==('実行'): #main.sub() threading.Thread(target=main5.sub, daemon=True) # ウィンドウの破棄と終了 window.close() <呼び出されるプログラム(main5.py)> import schedule import time def sub(): def job(): print("おはよう!") schedule.every().day.at("08:00").do(job) while True: schedule.run_pending() time.sleep(60)
teamikl

2021/05/18 03:56 編集

コメント内ではインデントが把握できないので、 質問のコードを編集でお願いします。 「うまくいかない」は、具体的にどのような挙動でしょうか? >elif event==('実行'):   #main.sub()   threading.Thread(target=main5.sub, daemon=True) 2重起動の防止は、ここのスレッドを実行するタイミングで行います。 スレッド生成で終わっているので、start()呼び出しが必要です。 (最初のコードにはありましたが、直前のコメントではstart が消えてます) thread = Thread(...) の省略してる部分は、実際のコードに置き換えてください。 ここは、その前の「2重起動を防止」の説明が要点だったので、 簡略化の為に省いてました。 >「thread = None 変数を準備しておく」 事前に用意してないと、if thread: で、未定義の変数になってしまうので、 while True: の前に変数を定義だけしておきます。
teamikl

2021/05/18 04:18

後、質問とは別問題ですが、気になった点 time.sleep(60) では、正常に稼働したとしても 最大59秒の誤差が発生する可能性がありますが、その点は大丈夫ですか? - schedule.idle_seconds() で次のスケジュール迄の秒数を得られます。 - time.sleep では、処理の中断が出来ないので、  実行途中でスケジュールの追加等、柔軟な対応を行いたい場合は  threading.Event() の wait()
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問