Python でプログラムを待機する方法を初心者向けに解説|time.sleepやasyncio、実務例も紹介
はじめに
Python で 待機 をするのは、一見すると単に「プログラムを一時停止させる」といった単純な行為に思えるかもしれません。
しかし、実際のプログラム開発では「時間調整のために処理を少し休ませる」だけではなく、外部のサービスやユーザーの操作を待ったり、イベントが発生するまで処理を進めないようにするなど、さまざまな場面で待機が必要になることがあります。
たとえば、以下のような状況で待機処理が用いられます。
- 定期的にAPIを呼び出す場合の実行タイミングのコントロール
- GUIアプリケーションでユーザーがボタンを押すのを待つ場合
- 複数のスレッドで進行する処理のタイミングを同期するとき
こうした実務での活用シーンにおいて、Python 待機の仕組みを正しく使いこなせると、プログラムの動きが安定して分かりやすいものになりやすいです。
逆に、どのように待機すべきかを曖昧にしてしまうと、思わぬバグやパフォーマンス上の問題を引き起こすことになりかねません。
本記事では、初心者の方が理解しやすいように、Python 待機の代表的な方法である time.sleep
、マルチスレッドを利用した待機、そして asyncio
を使った非同期での待機について解説します。
実務のシーンを交えながら、待機処理がどのように活用されるかを見ていきましょう。
この記事を読むとわかること
- Python で待機が必要になる代表的なシチュエーション
- time.sleep を使った基本的な待機処理の方法
- マルチスレッドを利用した待機や、イベント駆動型の待機方法
- asyncio による非同期待機のメリットと使い方
- 実務でよくあるケースを想定した待機と例外処理の考え方
- 待機によるパフォーマンスへの影響と注意点
Pythonにおける待機が必要なシーン
プログラミングを進めるうえで、待機がどんな場面で役立つのかを理解しておくことはとても大切です。
ここでは、実務でも登場しやすいシチュエーションをいくつか紹介します。
定期的にタスクを実行したいとき
Pythonで書いたスクリプトを一定間隔で実行したいケースはよくあります。
たとえば、サーバー監視スクリプトを5分おきに動かしてリソースの状態をチェックしたり、システムのログからエラーを検出して通知する仕組みを1時間に1回のペースで繰り返す、といった場面です。
こうした処理では、タスクが終わるごとに「次の実行時間まで待つ」動作が求められます。
ここで time.sleep
を使えば、非常にシンプルに待機を実装できるでしょう。
ただし、単に時間を寝かせるだけでは、長い実行時間がかかるタスクとの組み合わせで思わぬタイミングずれが発生する可能性もあります。
このように、待機自体をどう扱うかが、タスク全体の正確さや効率に影響を与えます。
外部リソースの応答を待機するとき
外部リソースとは、たとえばAPIサーバーやデータベース、またはユーザーの操作などを指します。
これらはシステムの外側にあるため、いつ結果が返ってくるのか分からないことがあります。
ネットワークの混雑や、相手先の処理負荷などによって応答が遅れると、こちらのプログラムとしては「外部からの応答が返るまで待機する」必要が出てきます。
応答が返るまで待機する仕組みは、コードを書くうえで工夫が必要です。
具体的には「待っている間に他のことを進めるか」「応答が来るまでひたすらブロックするか」の違いがあります。
単純にブロッキングしてしまうと、プログラムが無駄に待ちぼうけになる可能性があるため、マルチスレッドや非同期処理の活用が検討されることも多いです。
time.sleepを使った待機の基本
Python 待機でまずはじめに頭に浮かぶのが、標準ライブラリで用意されている time.sleep
です。
この関数は引数に与えられた秒数だけ処理を停止させる、いわば最もシンプルな待機方法として知られています。
time.sleepの使い方
基本的には time.sleep(秒数)
と書くだけの単純な構文です。
たとえば5秒待たせたい場合は以下のようになります。
import time print("処理を開始します。") time.sleep(5) print("5秒が経過しました。")
このコードを実行すると、最初のメッセージを表示したあと、プログラムは5秒間停止します。
その後に「5秒が経過しました。」のメッセージが表示されます。
処理を一時停止させるだけなら、非常に直感的で分かりやすい方法です。
引数には小数を与えることもできるので、0.5秒だけ待機させたい場合なども簡単に実現できます。
ただし time.sleep
は割り込むことができない同期的な待機となるため、待機中に他の作業を同時進行で進めたい場合は後述する別のアプローチが必要になるケースがあります。
time.sleepと実務での活用場面
定期実行系のシナリオで活躍しやすいのが time.sleep
です。
先ほど例に挙げたように、一定間隔でサーバーのステータスを取得したい場合や、短時間おきにログをチェックしてメール通知を送るといった場面で重宝します。
また、単なるテスト用途として「動作に遅延を入れて処理がぶつからないか確認したい」といったシチュエーションでもよく使われます。
本番環境で動く大規模なシステムでも、部分的にスクリプトを time.sleep
で待機させて「想定どおりの振る舞いをするか」を検証することがあります。
こういった応用は初心者でも比較的分かりやすい反面、あまり乱用すると他の処理が足止めを食らってしまうため、最適な方法かどうか見極めることが重要になります。
マルチスレッドでの待機
単一のスレッドで time.sleep
を使うと、待機しているあいだは他の処理が基本的に止まってしまうという問題点があります。
複数の処理を同時に進めたい場合や、待機中に別の仕事を進めたい場合には、マルチスレッドが有効な手段となります。
threadingモジュールの基本
Pythonには組み込みライブラリとして threading
モジュールが用意されています。
これはスレッドを扱うための仕組みで、同時並行的にタスクを走らせたいときに便利です。
同時並行とはいっても、PythonにはGIL(Global Interpreter Lock)という仕組みがあるため、CPUコアをフルに使った真の並列とは少し異なる点に注意が必要です。
単純にスレッドを立ち上げて time.sleep
を活用すれば、あるスレッドが待機しているあいだでも、別のスレッドで作業を続けられます。
例として、複数のスレッドを使っていろいろなサイトからデータを取得し、取得が終わるまで待機するプログラムをイメージしてみましょう。
import time import threading def fetch_data(site_name, wait_time): print(f"{site_name}からデータ取得を開始します。") time.sleep(wait_time) print(f"{site_name}からデータ取得が完了しました。") if __name__ == "__main__": thread_list = [] sites = [("SiteA", 2), ("SiteB", 5), ("SiteC", 3)] for site, wtime in sites: t = threading.Thread(target=fetch_data, args=(site, wtime)) t.start() thread_list.append(t) for t in thread_list: t.join() print("すべてのデータ取得が完了しました。")
上記の例では、各サイトのデータ取得を別々のスレッドで行っています。
スレッド内では time.sleep
を使ってあたかも「取得処理に時間がかかる」状況を模擬しているだけですが、メインスレッドは join()
メソッドを呼ぶまでは待機しつつ、スレッド同士は並行して処理を進めます。
こうした形で、単なる time.sleep
と組み合わせるだけでも、待機と別処理の並行実行を両立しやすくなります。
イベント駆動型の待機
マルチスレッドの話題でよく登場するのが、イベント を使った待機の仕組みです。
Pythonの threading
モジュールには Event
クラスがあり、これは「イベントを待つ」あるいは「イベントをセットして他のスレッドを再開させる」ために使われます。
具体的には、以下のようなコードでイベントを使った待機が実現できます。
import threading event = threading.Event() def worker(): print("ワーカーはイベントを待機しています。") event.wait() # イベントがセットされるまで待ち続ける print("イベントがセットされました。ワーカーが再開します。") if __name__ == "__main__": t = threading.Thread(target=worker) t.start() input("Enterキーを押すとイベントをセットします。:") event.set() t.join()
上記例では、worker
関数内で event.wait()
が呼ばれており、イベントがセットされるまで処理は停止します。
メインスレッド側で event.set()
を呼び出すと、その瞬間に待機が解除され、worker
関数の処理が再開します。
これは、外部の合図や条件が整うのを待ってから処理を進めたい場面にぴったりです。
実務では、あるデータ処理が完了し次第別のタスクを進めたいときなどに活用するケースがあります。
単に時間を置いて待つよりも、明確な合図で処理再開を制御できるため、動作をシンプルに設計しやすいというメリットがあります。
asyncioを使った非同期処理での待機
Python 3系には、非同期処理 を行うための機能として asyncio
フレームワークが標準で用意されています。
asyncio
を使うと、イベントループの上で複数のタスクを切り替えながら処理を実行できるため、待機が発生している間にも別のタスクを進行させることが可能です。
asyncioの基本
asyncio
では、関数定義に async
キーワードを使い、待機が必要な個所で await
を使います。
このように書くことで「この箇所で待機が必要なので他の処理に切り替えてOKですよ」という合図をイベントループに与えることができるわけです。
以下は、複数の非同期タスクを同時に走らせる簡単な例です。
import asyncio async def fetch_data(name, delay): print(f"{name} のデータ取得を開始します。") await asyncio.sleep(delay) print(f"{name} のデータ取得が完了しました。") async def main(): tasks = [ asyncio.create_task(fetch_data("SourceA", 2)), asyncio.create_task(fetch_data("SourceB", 3)), asyncio.create_task(fetch_data("SourceC", 1)), ] await asyncio.gather(*tasks) asyncio.run(main())
上記では、各タスク内で asyncio.sleep
を使って処理を待機させていますが、スレッドとは異なり、待機している間に別タスクへ処理が切り替わります。
こうすることで、実務でよくある「多数のAPIを順番に呼び出していたら、単なる待機時間ばかり増えてしまう」という無駄を削減することができます。
asyncio.sleepの特徴
asyncio.sleep
は time.sleep
とは異なり、呼び出し元のタスクだけが待機状態に入り、他のタスクはその時間を活用して処理を続けられます。
これは処理効率を高めるうえで大きな強みになります。
特にネットワークを介する処理は待機時間が読みづらいため、同期的に行うと待ち時間が膨れ上がりがちです。
非同期であれば、他のタスクが処理を進めてくれるので、待機の影響を小さく抑えやすくなります。
実務では、データベースやファイルI/Oなどのブロッキング処理を伴うコードを注意深く書かないと、非同期で書いたつもりが結局ブロックしてしまうというケースもあります。
非同期の待機を活用する際には、I/O操作を行うライブラリが非同期対応かどうかも確認するようにしましょう。
GUIアプリケーションでの待機
PythonでGUIアプリケーションを作る場合にも、待機の扱いは大切です。
代表的なライブラリとしては tkinter
や PyQt
、wxPython
などがありますが、これらはいずれも「イベントループ」を持っています。
イベントループをうまく使って待機させないと、ウィンドウが固まってしまう(フリーズする)問題が発生しがちです。
イベントループと待機
GUIプログラムでは、ボタンが押された、マウスが動いた、画面がリサイズされた、などのイベントをループで拾って処理する仕組みが基本です。
ここで time.sleep
を使ってしまうと、イベントループ自体が止まってしまい、画面がフリーズして操作不能になることがあります。
そのため、GUIツールキットごとに用意されている「一定時間後に関数を呼び出す仕組み」を活用するほうが自然です。
例えば tkinter
では after(ミリ秒, コールバック)
というメソッドがあり、これを使えば待機時間後にコールバック関数を実行できます。
こうすることで、Python 待機を実装しつつ、メインのイベントループは止めずに済むわけです。
マルチスレッドと併用する場合
GUIプログラムでも、マルチスレッドを使って重い処理を別スレッドに逃がし、そのあいだに画面操作を継続させるという方法があります。
ただし、スレッド間でGUIコンポーネントに直接アクセスすると不安定になることがあるため、画面操作はメインスレッドで行い、バックグラウンドの処理を別スレッドに任せるなどの設計が必要です。
スレッドで動かした処理が終わるのを待機するには、前述の Event
クラスや Queue
クラスなどを活用することも多いでしょう。
何らかの完了フラグが立てばメインスレッドで画面表示を更新し、待機を解除するような流れにすることで、ユーザー体験を損なわずに待機が実装できます。
バッチ処理や定期実行スクリプトでの待機
サーバーやクラウド上で動かすPythonスクリプトにも、待機の出番は多々あります。
定期的に実行されるバッチ処理では、指定の時間帯になったら処理を開始し、終わったら次の実行タイミングまで待つ、という流れが典型的です。
ファイル処理を伴う待機
バッチ処理の中にはファイルI/Oを扱うものも少なくありません。
たとえば、大きなCSVファイルを分割して処理したあと、ほかの処理が終わるのを待って再度読み込む、といったシナリオです。
この場合、単純に time.sleep
で待機するだけではなく、ファイルが用意されるのを待つ必要もあるかもしれません。
たとえば、別のシステムからファイルがアップロードされてくるタイミングが分からない時には「ファイルが見つかるまで待ち続ける」というロジックが必要です。
import time import os def wait_for_file(filepath, interval=5): while not os.path.exists(filepath): print(f"{filepath} がまだ存在しません。{interval}秒待機します。") time.sleep(interval) print(f"{filepath} が見つかりました。処理を続行します。") if __name__ == "__main__": wait_for_file("/path/to/input.csv") # ファイルが揃ったら後続の処理へ
上記の例はシンプルですが、実務では「ファイルが空じゃないか」「アクセス権限が適切か」なども同時にチェックすることが多いです。
このように待機にファイルシステムの状態確認を組み合わせることで、柔軟なコントロールが可能になります。
ネットワークアクセスを伴う待機
バッチ処理では外部のAPIを呼び出したり、データベースに接続して大きなデータを読み書きしたりすることもあります。
ネットワークを介した操作は、遅延が大きい場合があるので、待機のロジックが重要になります。
たとえば、あるAPIへ連続でリクエストを送るとレートリミットに引っかかるケースがあります。
このときは、一定の時間を空けて再リクエストを行うために time.sleep
を使うことがあるでしょう。
ただ、待っている間にも他の処理を進めたいのであれば、マルチスレッドや非同期処理を検討するのがベターです。
もしネットワークが不安定で、レスポンスが一定時間返ってこない場合を想定するなら、後述するタイムアウトやリトライの実装も考慮する必要があります。
こうしたケースでは待機の仕組みと、次の動きをどう制御するかがセットになってきます。
待機処理と例外処理の組み合わせ
待機と例外処理はセットで考えると、プログラムの安定性が高まります。
「待っていたけど一定時間応答がない」「待っている間に処理が失敗した」といった状況にどう対応するかを決めておくと、想定外のエラーでプログラムが止まるのを防ぎやすくなるからです。
待機中のエラーをどう扱うか
たとえば、time.sleep
中にシグナルを受け取ったらどうするのか、マルチスレッドでイベント待機中にキーボード割り込みが起こったらどうなるかなど、実務ではいろいろなケースが起こりえます。
一般的には、try-except
ブロックで例外をキャッチしてログを出す、または安全に終了処理を進めるロジックを組み込むことが多いです。
asyncio
の場合でも同様に、await
している最中にエラーが起きる場合があります。
そのため、async def
関数の中でも try-except
を使い、エラー発生時にどのように振る舞うかを明示的に書いておくのがおすすめです。
タイムアウトの導入
待機処理でよくある工夫として、「どこかでタイムアウトさせる」という方法があります。
無制限に待ち続けるのではなく、一定時間経過してもイベントが起きなかったり、外部リソースから応答がなかったりするときには、何らかの行動を取るわけです。
threading
の Event
クラスにはタイムアウト付きの wait
が用意されています。
例えば event.wait(timeout=10)
のように書くと、最大10秒まで待機して、イベントがセットされない場合は False
を返して処理を進めます。
asyncio
の場合は、asyncio.wait_for
を使えばタイムアウトの仕組みを簡単に導入できます。
import asyncio async def long_task(): print("長時間のタスクを開始します。") await asyncio.sleep(5) # ここで5秒の待機 print("長時間のタスクが完了しました。") async def main(): try: await asyncio.wait_for(long_task(), timeout=3) except asyncio.TimeoutError: print("タイムアウトしました。別の処理に切り替えます。") asyncio.run(main())
上記の例では、タスクは5秒かかる想定ですが、3秒のタイムアウトを指定しているため TimeoutError
が発生して処理が中断されます。
こうして制御構文を加えることで、待機にまつわるリスクヘッジがしやすくなるのが特徴です。
Python 待機の注意点
待機処理は便利ですが、誤った使い方をするとパフォーマンス低下やユーザーの不満を招きやすいです。
ここでは注意すべきポイントをいくつか取り上げます。
過剰な待機がパフォーマンスに与える影響
単に time.sleep
で間隔を空けすぎると、「なぜか処理がなかなか終わらない」あるいは「システム全体が遅延している」などの原因になることがあります。
もちろん必要な待機は必要ですが、過剰な秒数を指定すると、ほかに処理を進めたくてもプログラム全体が眠ってしまいます。
逆に待ち時間を極端に短くすると、今度は何度もポーリングを行ってCPUを無駄に消費することがあり得ます。
定期実行が目的のスクリプトでも、本当に秒単位での高頻度チェックが必要なのかどうか、あらかじめ検討しておきたいところです。
マルチスレッドや非同期処理であっても、待機が多すぎるとスレッドやタスクが増えすぎて管理が複雑化するなどの弊害が出る可能性があります。
必要な待機と不要な待機を仕分けることで、スクリプトの効率を高めることが大切です。
待機処理のテスト方法
待機を含むコードはテストが難しいイメージがあるかもしれませんが、工夫次第で比較的スムーズに進められます。
たとえば、タイマー関連の箇所をモック化して、本来の待機時間をカットしたテストを行うというアプローチが一般的です。
これにより、長時間の待機が必要なケースでも、実際には即座にテストを終了させられるので開発効率が上がります。
また、イベントを使った待機の場合は、テストコードの中で明示的に event.set()
を呼ぶなどして、特定の状況を再現するテクニックがあります。
こうしたやり方を組み合わせることで、待機時間そのものを短縮しながら、実際のフローを疑似的にテストすることが可能です。
テストやデバッグの段階では意図的に待機を入れてみて、思わぬタイミングで障害が出ないかを確認するのも一つの手です。
ただし、本番コードに無駄な待機が残らないように管理しておきましょう。
まとめ
ここまで、Python 待機に関するさまざまな方法を紹介してきました。
time.sleep
のようにシンプルな手法は理解しやすい反面、処理全体を止めてしまうため、マルチスレッドや非同期処理が必要なシーンでは注意が必要です。
一方、マルチスレッドや asyncio
を利用することで、待機中も別のタスクを進めることが可能になります。
実務では、例えば定期的なバッチ処理をしながら外部サービスに問い合わせる場合や、GUIアプリケーションでユーザー操作を待つ場合など、状況に応じて使い分けられると便利です。
time.sleep
は同期的な待機の基本threading
を利用すればマルチスレッド環境でも待機を制御できるasyncio
なら待機中も効率よく他のタスクを並行処理できる- 実務ではイベントやタイムアウトを組み合わせた待機が多用される
どの方法も一長一短があり、プログラムの要件によって最適解は変わります。
待機が必要となる場面を明確にしつつ、過剰な待機や不必要な同期を避けるよう設計を行ってみてください。
これらを押さえたうえで、Python 待機を活用すると、動きが分かりやすく無駄の少ないプログラムを書きやすくなるはずです。