質問するログイン新規登録

Q&A

解決済

1回答

426閲覧

Python scheduleモジュールを使用しての定周期処理

keruuuu

総合スコア17

0グッド

0クリップ

投稿2024/12/05 03:25

編集2024/12/05 03:32

0

0

実現したいこと

任意の間隔での関数の定周期実行
前回処理開始時 - (インターバル) - 今回処理開始

発生している問題・分からないこと

指定したインターバル間隔が処理終了時~次回処理開始となってしまう。
前回処理開始時 - 前回処理終了時 - (インターバル) - 今回処理開始

該当のソースコード

Python

1import time 2import schedule 3import datetime as dt 4 5INTERVAL = 10 6 7def main(): 8 scheduler = schedule.Scheduler() 9 scheduler.every(INTERVAL).seconds.do( 10 heavy_function 11 ) 12 13 while True: 14 scheduler.run_pending() 15 time.sleep(0.1) 16 17 18def heavy_function(): 19 start_time = dt.datetime.now(tz=dt.timezone.utc) 20 print(f'start: {start_time}') 21 time.sleep(5) 22 end_time = dt.datetime.now(tz=dt.timezone.utc) 23 print(f'end: {end_time}') 24 25 26if __name__ == '__main__': 27 main()

Condole

1start: 2024-12-05 03:22:14.116578+00:00 2end: 2024-12-05 03:22:19.121740+00:00 3start: 2024-12-05 03:22:29.145224+00:00 4end: 2024-12-05 03:22:34.150440+00:00 5start: 2024-12-05 03:22:44.174894+00:00 6end: 2024-12-05 03:22:49.180033+00:00

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

実現したいことを実施する方法が見つかりませんでした。

補足

特になし

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

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

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

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

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

poto568

2024/12/05 04:22

私の環境では「python schedule」でgoogle検索すると以下の記事が 6番目に表示されています。(私が書いた記事ではありません。) https://progzakki.sanachan.com/program-lang/python/schedule-job-periodically/ 続きはこちら。 https://progzakki.sanachan.com/program-lang/python/signal-handler-periodically/ python単体で完結できませんが、cronとかタスクスケジューラなんかと 併用しても良いかもしれません。
bsdfan

2024/12/05 08:33

「scheduleモジュールを使用して」というのが必須なら難しいと思います。jobを別スレッドにする方法もありますが、起動時間のズレが積み重なっていくのは消せません。(それぐらいのズレが許容できるならscheduleでスレッドなりを使えばいいと思います) 他のライブラリでもいいなら、apscheduler でご希望の動作ができると思います。
guest

回答1

0

ベストアンサー

指定したインターバル間隔が処理終了時~次回処理開始となってしまう。

スケジューラーでは呼び出しのみに留めて、実際の処理は別スレッドで行うようにします。
schedule ライブラリでは、(補足追記: スケジューラーと同一スレッドで実行される)タスク内で time.sleep のようなスケジューラーを阻害するようなことはしてはいけません。

schedule は元々そのような設計で、
ドキュメントの一番上に注意書きがあります。DeepL翻訳

Scheduleを使用しない場合:

正直に言うと、Scheduleは'万能'なスケジューリングライブラリではありません。
このライブラリは単純なスケジューリング問題のための単純なソリューションとして設計されています。
もし必要であれば他をあたるべきでしょう:

  • ジョブの永続性(再スタート時にスケジュールを覚えておく)
  • 正確なタイミング(秒以下の精度での実行)
  • 同時実行(複数のスレッド)
  • ローカライゼーション(平日や休日)

スケジュールはジョブ機能の実行時間を考慮しません。
安定した実行スケジュールを保証するには、
長時間実行するジョブをメインスレッド(スケジューラが実行されるスレッド)から移動させる必要があります。> 実装のサンプルは 並列実行 を参照してください。

※ 注意すべきは、リストとして挙がっている4項目に該当する場合が、「使用しない場合」
つまり、schedule ライブラリ側では対応していない項目です。
追記: 翻訳で意図が伝わりにくくなってるかもしれないので、原文も参考にしてください。

解決策(簡易版)

python

1def as_background(func): 2 from threading import Thread 3 from functools import wraps 4 5 @wraps(func) 6 def _func(): 7 thread = Thread(target=func, daemon=True) 8 thread.start() 9 10 return _func 11 12 13@as_background 14def heavy_function(): 15 ... 16

この実装では毎回スレッドを生成しますが、秒単位の制度が必要な場合は問題になるかもしれません。
予め複数のスレッドを稼働させておき、同期queue等でメッセージを送ることにより、
遅延をさらに少なくすることができます。⇒ 上記リンクにqueueを用いた実装例もあり

別ライブラリの APScheduler なら、デフォルトでそのような実装になってます。
(メインスレッドでスケジューラー稼働、別スレッドでジョブ実行)

他に、精度を確保するアイデアとしては、折角のコードの可読性が損なわれてしまいますが、
定期的にタスク登録用のタスクを用いることで、遅延をリセットします
例えば、毎時 0分に なったら、10秒後20秒後...にジョブが実行されるように複数登録。

解決策2: スレッドプールを用いた実装 (apscheduler と同様の挙動・精度)

python

1import time 2from datetime import datetime 3from threading import Thread 4from queue import Queue 5from concurrent.futures import ThreadPoolExecutor 6import logging 7import schedule 8 9logger = logging.getLogger(__name__) 10 11 12class BackgroundWorker: 13 def __init__(self, *args, **kw): 14 15 # NOTE: daemon-thread, will not wait task completed 16 queue = self.queue = Queue() 17 thread = self.thread = Thread( 18 target=self.run, 19 args=(queue,), 20 name="worker", 21 daemon=True) 22 thread.start() 23 24 def __call__(self, func): 25 def _job(*args, **kw): 26 self.queue.put((func, args, kw)) 27 return _job 28 29 def __del__(self): 30 if self.thread: 31 self.queue.put(None) 32 self.thread.join() 33 34 def run(self, queue): 35 logger.info("start") 36 with ThreadPoolExecutor( 37 max_workers=5, 38 thread_name_prefix="worker") as executor: 39 for func, args, kw in iter(queue.get, None): 40 executor.submit(func, *args, **kw) 41 logger.info("end") 42 43 44def main(): 45 worker = BackgroundWorker() 46 47 @worker 48 def job(): 49 logger.debug(f"I'm working... {datetime.now()}") 50 time.sleep(5) 51 logger.debug(f"end {datetime.now()}") 52 53 schedule.every(10).seconds.do(job) 54 55 try: 56 while True: 57 schedule.run_pending() 58 time.sleep(1) 59 except (KeyboardInterrupt, SystemExit) as e: 60 pass 61 62 63if __name__ == '__main__': 64 logging.basicConfig( 65 level=logging.DEBUG, 66 format="[%(threadName)-10s][%(levelname)-8s] %(message)s", 67 68 ) 69 main()

投稿2024/12/05 07:52

編集2024/12/05 08:03
teamikl

総合スコア8824

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

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

keruuuu

2024/12/10 02:04

丁寧なご回答ありがとうございます。 APSchedulerを使用することで解決できました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.30%

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

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

質問する

関連した質問