C#プログラムを開発する際、特定の条件が満たされるまで処理を繰り返したり、アプリケーションが終了するまで常時待機状態を維持したりするために「無限ループ」が必要になる場面があります。

例えば、サーバーの待機処理、ゲームのメインループ、あるいはセンサーデータの継続的な監視などが挙げられます。

しかし、無限ループは正しく制御しなければCPUのリソースを過剰に消費したり、プログラムがフリーズしたりする原因にもなり得ます。

本記事では、C#における無限ループの代表的な実装方法から、安全にループを抜けるための制御手法、そしてパフォーマンスを損なわないための回避策まで、プロフェッショナルな視点で詳しく解説します。

C#における無限ループの基本実装

C#で無限ループを実装する方法はいくつか存在しますが、一般的に用いられるのは while 文や for 文です。

それぞれの記述方法と特徴を確認していきましょう。

while(true) による実装

最も直感的で頻繁に利用されるのが while (true) を使用する方法です。

条件式に常に真となるリテラルを指定することで、ブロック内の処理を永続的に繰り返します。

C#
using System;

class Program
{
    static void Main()
    {
        // while(true) による無限ループの例
        while (true)
        {
            Console.WriteLine("ループを実行中...");
            
            // 実際にはここに終了条件やスリープを記述します
            break; // サンプルのため1回で抜けます
        }
    }
}

この記述はコードの意図が明確であり、可読性が高いというメリットがあります。

後述するフラグ変数を用いた制御にも応用しやすいため、C#開発において第一選択となる手法です。

for(;;) による実装

C言語の流れを汲む記法として、for (;;) という書き方もあります。

初期化式、条件式、更新式のすべてを省略することで無限ループを形成します。

C#
using System;

class Program
{
    static void Main()
    {
        // for(;;) による無限ループの例
        for (;;)
        {
            Console.WriteLine("for文による無限ループです");
            break; // サンプルのため1回で抜けます
        }
    }
}

機能的には while (true) と全く同じですが、現代のC#開発では可読性の観点から while(true) が好まれる傾向にあります。

ただし、既存のライブラリや古いソースコードでは見かけることもあるため、知識として持っておくべきでしょう。

無限ループを安全に抜けるための制御策

意図的な無限ループであっても、特定のイベントが発生した際やユーザーが停止を求めた際には、安全にループを終了させる必要があります。

ここでは、ループを制御する3つの主要な方法を解説します。

break文による強制終了

最もシンプルな方法は、ループ内の if 文と break キーワードを組み合わせる手法です。

C#
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 型の変数を使用することで、外部からの指示によってループを終了させることができます。

C#
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 を使用するのが最も推奨されるベストプラクティスです。

C#
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による負荷軽減

C#
while (true)
{
    // 何らかの監視処理
    CheckStatus();

    // 100ミリ秒待機することで、CPU負荷を劇的に抑える
    await Task.Delay(100);
}

たとえ 1ms であっても待機を入れるだけで、CPU使用率は劇的に改善されます。

リアルタイム性が極限まで求められるゲームエンジンのような特殊なケースを除き、無限ループには必ず待機処理を含めるべきです。

意図しない無限ループとその回避策

バグとして発生する「意図しない無限ループ」は、システムダウンに直結する危険な存在です。

特に以下のケースには注意が必要です。

1. カウンタ変数の更新忘れ

while 文でカウンタを使用している場合、更新式を忘れると無限ループに陥ります。

C#
int i = 0;
while (i < 10)
{
    Console.WriteLine(i);
    // i++; を忘れると無限ループ
}

これを防ぐには、可能な限り for 文や foreach 文を使用し、反復の範囲を限定する設計にすることが重要です。

2. 再帰呼び出しによる無限ループ(StackOverflow)

メソッドが自分自身を呼び出し続ける「無限再帰」も、一種の無限ループです。

これは最終的に StackOverflowException を引き起こし、アプリケーションをクラッシュさせます。

C#
void RecursiveMethod()
{
    // 終了条件がないため、スタックが溢れるまで呼び出し続ける
    RecursiveMethod();
}

再帰処理を書く際は、必ず「ベースケース(終了条件)」が最初に評価されるように設計してください。

実践的な活用例:簡易的なログ監視プログラム

最後に、これまで解説した知識を統合した、安全な無限ループのサンプルプログラムを紹介します。

このプログラムは、ファイルの内容を継続的にチェックし、新しい書き込みがあれば表示するものです。

C#
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#における無限ループは、適切な実装と制御を組み合わせることで、非常に強力なツールとなります。

実装時には以下のポイントを常に意識しましょう。

  1. 可読性重視なら while (true) を選択する。
  2. 非同期処理では CancellationToken を使用して安全に停止させる。
  3. パフォーマンス維持のため、必ず Task.Delay 等でCPUを休ませる。
  4. 意図しないループを防ぐため、カウンタの更新や再帰の終了条件を徹底的に確認する。

無限ループを「恐れるべきバグ」ではなく「制御可能な仕組み」として扱うことが、堅牢なアプリケーション開発への第一歩です。

今回の内容を参考に、安全で効率的なループ処理を実装してみてください。