C#プログラムを開発する際、特定の条件が満たされるまで処理を繰り返したり、アプリケーションが終了するまで常時待機状態を維持したりするために「無限ループ」が必要になる場面があります。
例えば、サーバーの待機処理、ゲームのメインループ、あるいはセンサーデータの継続的な監視などが挙げられます。
しかし、無限ループは正しく制御しなければCPUのリソースを過剰に消費したり、プログラムがフリーズしたりする原因にもなり得ます。
本記事では、C#における無限ループの代表的な実装方法から、安全にループを抜けるための制御手法、そしてパフォーマンスを損なわないための回避策まで、プロフェッショナルな視点で詳しく解説します。
C#における無限ループの基本実装
C#で無限ループを実装する方法はいくつか存在しますが、一般的に用いられるのは while 文や for 文です。
それぞれの記述方法と特徴を確認していきましょう。
while(true) による実装
最も直感的で頻繁に利用されるのが while (true) を使用する方法です。
条件式に常に真となるリテラルを指定することで、ブロック内の処理を永続的に繰り返します。
using System;
class Program
{
static void Main()
{
// while(true) による無限ループの例
while (true)
{
Console.WriteLine("ループを実行中...");
// 実際にはここに終了条件やスリープを記述します
break; // サンプルのため1回で抜けます
}
}
}
この記述はコードの意図が明確であり、可読性が高いというメリットがあります。
後述するフラグ変数を用いた制御にも応用しやすいため、C#開発において第一選択となる手法です。
for(;;) による実装
C言語の流れを汲む記法として、for (;;) という書き方もあります。
初期化式、条件式、更新式のすべてを省略することで無限ループを形成します。
using System;
class Program
{
static void Main()
{
// for(;;) による無限ループの例
for (;;)
{
Console.WriteLine("for文による無限ループです");
break; // サンプルのため1回で抜けます
}
}
}
機能的には while (true) と全く同じですが、現代のC#開発では可読性の観点から while(true) が好まれる傾向にあります。
ただし、既存のライブラリや古いソースコードでは見かけることもあるため、知識として持っておくべきでしょう。
無限ループを安全に抜けるための制御策
意図的な無限ループであっても、特定のイベントが発生した際やユーザーが停止を求めた際には、安全にループを終了させる必要があります。
ここでは、ループを制御する3つの主要な方法を解説します。
break文による強制終了
最もシンプルな方法は、ループ内の if 文と break キーワードを組み合わせる手法です。
using System;
class Program
{
static void Main()
{
Console.WriteLine("数字を入力してください('0'で終了):");
while (true)
{
string input = Console.ReadLine();
if (input == "0")
{
Console.WriteLine("ループを終了します。");
break; // ループを抜ける
}
Console.WriteLine($"入力された値: {input}");
}
}
}
この方法は、ループの内部ロジックで終了判断が完結する場合に非常に有効です。
制御用フラグ変数の活用
ループの条件式に bool 型の変数を使用することで、外部からの指示によってループを終了させることができます。
using System;
using System.Threading.Tasks;
class Program
{
private static bool _isRunning = true;
static async Task Main()
{
// 別タスクで5秒後に停止フラグを立てる
Task.Run(async () =>
{
await Task.Delay(5000);
_isRunning = false;
Console.WriteLine("停止フラグがセットされました。");
});
while (_isRunning)
{
Console.WriteLine("稼働中...");
await Task.Delay(1000);
}
Console.WriteLine("プログラムを正常に終了しました。");
}
}
マルチスレッド環境では、フラグ変数に volatile 修飾子を付与するか、スレッドセーフな型を使用することを検討してください。
CancellationTokenによる最新の制御
現代的な.NET開発(特に非同期処理)においては、CancellationToken を使用するのが最も推奨されるベストプラクティスです。
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
using var cts = new CancellationTokenSource();
// 3秒後に自動的にキャンセルを発行
cts.CancelAfter(3000);
try
{
await DoWorkAsync(cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("キャンセル要求により停止しました。");
}
}
static async Task DoWorkAsync(CancellationToken token)
{
while (true)
{
// キャンセルが要求されているか確認
token.ThrowIfCancellationRequested();
Console.WriteLine("重い処理を実行中...");
await Task.Delay(500, token); // Delayにもトークンを渡す
}
}
}
CancellationToken を使うことで、複数のタスクに対して一括で停止命令を送ることができ、リソースのクリーンアップも容易になります。
CPU負荷を抑えるための待機処理
無限ループを記述する際、最も注意しなければならないのが「CPUの占有」です。
何も対策をせずに空の無限ループを実行すると、CPUの1コアが100%の使用率に達し、PCの動作が重くなったり、バッテリーを激しく消耗したりします。
これを回避するためには、適切な「待機」を挿入する必要があります。
| 待機方法 | 特徴 | 推奨シーン |
|---|---|---|
Thread.Sleep(ms) | 現在のスレッドを完全に停止させる。 | コンソールアプリや同期的な処理。 |
Task.Delay(ms) | 非同期的に待機し、スレッドを解放する。 | GUIアプリやWeb API(推奨)。 |
SpinWait | 非常に短い時間の待機を行う。 | 超低遅延が求められる特殊なケース。 |
実装例:Task.Delayによる負荷軽減
while (true)
{
// 何らかの監視処理
CheckStatus();
// 100ミリ秒待機することで、CPU負荷を劇的に抑える
await Task.Delay(100);
}
たとえ 1ms であっても待機を入れるだけで、CPU使用率は劇的に改善されます。
リアルタイム性が極限まで求められるゲームエンジンのような特殊なケースを除き、無限ループには必ず待機処理を含めるべきです。
意図しない無限ループとその回避策
バグとして発生する「意図しない無限ループ」は、システムダウンに直結する危険な存在です。
特に以下のケースには注意が必要です。
1. カウンタ変数の更新忘れ
while 文でカウンタを使用している場合、更新式を忘れると無限ループに陥ります。
int i = 0;
while (i < 10)
{
Console.WriteLine(i);
// i++; を忘れると無限ループ
}
これを防ぐには、可能な限り for 文や foreach 文を使用し、反復の範囲を限定する設計にすることが重要です。
2. 再帰呼び出しによる無限ループ(StackOverflow)
メソッドが自分自身を呼び出し続ける「無限再帰」も、一種の無限ループです。
これは最終的に StackOverflowException を引き起こし、アプリケーションをクラッシュさせます。
void RecursiveMethod()
{
// 終了条件がないため、スタックが溢れるまで呼び出し続ける
RecursiveMethod();
}
再帰処理を書く際は、必ず「ベースケース(終了条件)」が最初に評価されるように設計してください。
実践的な活用例:簡易的なログ監視プログラム
最後に、これまで解説した知識を統合した、安全な無限ループのサンプルプログラムを紹介します。
このプログラムは、ファイルの内容を継続的にチェックし、新しい書き込みがあれば表示するものです。
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
class LogMonitor
{
static async Task Main()
{
using var cts = new CancellationTokenSource();
Console.WriteLine("ログ監視を開始しました(Ctrl+Cで終了)...");
// Ctrl+Cで安全にキャンセル
Console.CancelKeyPress += (s, e) =>
{
e.Cancel = true;
cts.Cancel();
};
try
{
await MonitorFileAsync("app.log", cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("\n監視を終了しました。");
}
}
static async Task MonitorFileAsync(string filePath, CancellationToken token)
{
long lastSize = 0;
while (true)
{
// キャンセル確認
token.ThrowIfCancellationRequested();
if (File.Exists(filePath))
{
var info = new FileInfo(filePath);
if (info.Length > lastSize)
{
Console.WriteLine($"[更新通知] ファイルサイズが変更されました: {info.Length} bytes");
lastSize = info.Length;
}
}
// 1秒待機(CPU負荷軽減)
await Task.Delay(1000, token);
}
}
}
実行結果のイメージ
ログ監視を開始しました(Ctrl+Cで終了)...
[更新通知] ファイルサイズが変更されました: 124 bytes
[更新通知] ファイルサイズが変更されました: 256 bytes
^C
監視を終了しました。
まとめ
C#における無限ループは、適切な実装と制御を組み合わせることで、非常に強力なツールとなります。
実装時には以下のポイントを常に意識しましょう。
- 可読性重視なら
while (true)を選択する。 - 非同期処理では
CancellationTokenを使用して安全に停止させる。 - パフォーマンス維持のため、必ず
Task.Delay等でCPUを休ませる。 - 意図しないループを防ぐため、カウンタの更新や再帰の終了条件を徹底的に確認する。
無限ループを「恐れるべきバグ」ではなく「制御可能な仕組み」として扱うことが、堅牢なアプリケーション開発への第一歩です。
今回の内容を参考に、安全で効率的なループ処理を実装してみてください。






