C#でスリープ処理を実装する方法|Thread.Sleep・Task.Delayの違いと使い分け

はじめに

C#で一定時間処理を止めたいとき、よく使われるのが「スリープ処理」です。たとえば「1秒待ってから次の処理を実行したい」「リトライ前に少し待機したい」「デバッグ中に処理の流れを確認したい」といった場面で利用されます。

C#のスリープ処理には、代表的な方法としてThread.SleepTask.Delayがあります。どちらも一定時間待機するために使えますが、仕組みや適した用途は大きく異なります。特に、UIアプリやWebアプリ、非同期処理では使い分けを誤ると、画面が固まったり、パフォーマンスが低下したりする原因になります。

この記事では、C#でスリープ処理を実装する方法を、Thread.SleepTask.Delayの違いや使い分けを中心に解説します。サンプルコードも交えながら、実務で安全に使うための考え方まで整理していきます。

1. C#でスリープ処理を使う前に知っておきたい基本

1-1. C#におけるスリープ処理とは

C#におけるスリープ処理とは、現在実行中の処理を一定時間待機させることです。

たとえば、次のような処理があるとします。

C#
Console.WriteLine("処理開始");
Thread.Sleep(1000);
Console.WriteLine("1秒後に実行");

この場合、Thread.Sleep(1000)によって約1秒間待機してから、次の処理が実行されます。

C#でスリープ処理を行う代表的な方法には、主に次のものがあります。

C#
Thread.Sleep(1000);
await Task.Delay(1000);

どちらも「1秒待つ」処理に見えますが、Thread.Sleepはスレッドを停止させる同期的な待機、Task.Delayは非同期的な待機です。この違いを理解することが、C#のスリープ処理を正しく使ううえで重要です。

1-2. スリープ処理が必要になる主な場面

C#でスリープ処理が使われる主な場面は、次のようなケースです。

処理の間隔を空けたい場合、たとえば一定時間ごとにAPIを呼び出す処理や、連続実行を避けたい処理でスリープが使われます。

C#
foreach (var item in items)
{
Console.WriteLine(item);
Thread.Sleep(500);
}

リトライ処理の前に待機したい場合にも使われます。外部APIやデータベース接続が一時的に失敗したとき、すぐに再実行するのではなく、少し待ってから再試行することで成功率が上がることがあります。

また、デバッグ目的で一時的に処理を止めたい場合にも便利です。ログの出力タイミングを確認したり、非同期処理の挙動を観察したりするために使うことがあります。

ただし、スリープ処理は便利な一方で、根本的な同期制御や処理完了待ちの代替として使うべきではありません。

1-3. スリープ処理でできること・できないこと

スリープ処理でできることは、あくまで「一定時間待つ」ことです。たとえば、1秒後に次の処理を実行したり、処理の実行間隔を空けたりする用途には向いています。

一方で、スリープ処理では「別の処理が完了したことを確実に検知する」ことはできません。

たとえば、次のようなコードは避けるべきです。

C#
StartBackgroundWork();

Thread.Sleep(3000);

UseResult();

このコードでは「3秒待てばバックグラウンド処理が終わっているはず」という前提になっています。しかし、処理が3秒以内に終わる保証はありません。環境や負荷によっては、まだ完了していない可能性があります。

処理完了を待つなら、Task、イベント、コールバック、同期オブジェクトなどを使うべきです。スリープは時間を待つためのものであり、状態変化や完了通知を待つためのものではありません。

1-4. 安易にスリープを使うと起きる問題

C#でスリープ処理を安易に使うと、いくつかの問題が発生します。

特にThread.Sleepは、現在のスレッドを完全にブロックします。UIスレッドで使うと画面が固まり、ボタン操作や描画更新が止まります。Webアプリで多用すると、リクエスト処理中のスレッドを占有し、同時接続数や応答性能に悪影響を与える可能性があります。

また、スリープ時間は厳密ではありません。Thread.Sleep(1000)と書いても、必ず正確に1000ミリ秒後に再開されるわけではなく、OSのスケジューリングや実行環境の負荷によって多少前後します。

さらに、スリープで処理順序を調整しようとすると、不安定なコードになりがちです。開発環境では動いていても、本番環境ではタイミングが変わって不具合になることがあります。

2. C#でスリープ処理を実装する代表的な方法

2-1. Thread.Sleepを使う方法

Thread.Sleepは、指定した時間だけ現在のスレッドを停止するメソッドです。

C#
Thread.Sleep(1000);

このコードは、現在のスレッドを約1秒間停止します。

Thread.Sleepはシンプルで分かりやすいため、コンソールアプリや一時的なデバッグ用途ではよく使われます。ただし、スレッドをブロックするため、UIアプリやWebアプリ、非同期処理では注意が必要です。

使用するには、次の名前空間を指定します。

C#
using System.Threading;

2-2. Task.Delayを使う方法

Task.Delayは、指定した時間が経過した後に完了するTaskを返すメソッドです。

C#
await Task.Delay(1000);

Task.Delayはスレッドをブロックせず、非同期的に待機できます。そのため、async/awaitを使うC#の非同期処理では、基本的にThread.SleepよりTask.Delayが適しています。

使用するには、次の名前空間を指定します。

C#
using System.Threading.Tasks;

Task.Delayは、UIアプリやWebアプリでも使いやすい待機方法です。待機中にスレッドを占有しないため、アプリ全体の応答性を保ちやすくなります。

2-3. Timerを使って一定時間後に処理する方法

一定時間後に処理を実行したい場合や、一定間隔で繰り返し処理を行いたい場合は、Timerを使う方法もあります。

C#
using System;
using System.Threading;

var timer = new Timer(_ =>
{
Console.WriteLine("1秒後に実行");
}, null, 1000, Timeout.Infinite);

Console.ReadLine();

この例では、1秒後に処理が実行されます。

Thread.SleepTask.Delayは「現在の処理の流れの中で待つ」方法ですが、Timerは「指定時間後に別の処理を呼び出す」方法です。定期実行や遅延実行には、Timerのほうが自然なケースがあります。

2-4. Wait系メソッドを使うケース

C#では、Task.WaitWaitHandle.WaitOneなどのWait系メソッドもあります。

C#
task.Wait();

これらは、一定時間待機するというよりも、タスクやシグナルの完了を待つために使われます。

たとえば、Task.Waitは非同期タスクの完了を同期的に待ちます。ただし、UIアプリやASP.NETなどで安易に使うとデッドロックやスレッドブロックの原因になることがあります。

非同期処理では、基本的にWait()ではなくawaitを使うほうが安全です。

C#
await task;

Wait系メソッドは、古い同期コードや低レベルなスレッド制御で使われることがありますが、通常のアプリケーション開発では慎重に扱うべきです。

2-5. スリープ処理の実装方法を比較する

C#のスリープ処理や待機処理は、目的によって選ぶ方法が変わります。

方法特徴主な用途
Thread.Sleep現在のスレッドをブロックするコンソールアプリ、簡単な待機、デバッグ
Task.Delayスレッドをブロックせず非同期で待機する非同期処理、UIアプリ、Webアプリ
Timer指定時間後または一定間隔で処理を実行する定期実行、遅延実行
Wait系メソッド完了やシグナルを同期的に待つ同期制御、低レベルな待機

単純に「C#で1秒待つ」だけなら、同期処理ではThread.Sleep(1000)、非同期処理ではawait Task.Delay(1000)が基本です。

ただし、現代的なC#開発では、スレッドをブロックしないTask.Delayを優先して検討するのが一般的です。

3. Thread.Sleepの使い方とサンプルコード

3-1. Thread.Sleepの基本構文

Thread.Sleepの基本構文は次のとおりです。

C#
Thread.Sleep(待機時間);

待機時間は、ミリ秒単位の整数またはTimeSpanで指定できます。

C#
Thread.Sleep(1000);
Thread.Sleep(TimeSpan.FromSeconds(1));

Thread.Sleepを使うには、次の名前空間が必要です。

C#
using System.Threading;

Thread.Sleepは現在実行中のスレッドを停止します。つまり、処理の実行を一時停止するだけでなく、そのスレッドでは他の作業もできなくなります。

3-2. ミリ秒単位で処理を停止する方法

ミリ秒単位で待機する場合は、整数値を指定します。

C#
Thread.Sleep(500);

このコードは約500ミリ秒、つまり0.5秒待機します。

よく使われる指定例は次のとおりです。

C#
Thread.Sleep(100);   // 0.1秒待機
Thread.Sleep(500); // 0.5秒待機
Thread.Sleep(1000); // 1秒待機
Thread.Sleep(3000); // 3秒待機

C#のThread.Sleepでは、引数に指定する数値は秒ではなくミリ秒です。Thread.Sleep(1)は1秒ではなく1ミリ秒なので注意しましょう。

3-3. TimeSpanを使って待機時間を指定する方法

待機時間を分かりやすく書きたい場合は、TimeSpanを使う方法がおすすめです。

C#
Thread.Sleep(TimeSpan.FromSeconds(1));
Thread.Sleep(TimeSpan.FromMilliseconds(500));
Thread.Sleep(TimeSpan.FromMinutes(1));

TimeSpanを使うと、単位が明確になります。

C#
Thread.Sleep(TimeSpan.FromSeconds(3));

このコードは3秒待機します。Thread.Sleep(3000)よりも、待機時間の意図が読み取りやすくなります。

長めの待機時間を指定する場合は、特にTimeSpanを使うと保守性が高くなります。

3-4. Thread.Sleep(1000)で1秒待機するサンプル

Thread.Sleep(1000)を使って1秒待機するサンプルです。

C#
using System;
using System.Threading;

class Program
{
static void Main()
{
Console.WriteLine("処理開始");

Thread.Sleep(1000);

Console.WriteLine("1秒後に処理再開");
}
}

実行すると、最初に「処理開始」と表示され、その約1秒後に「1秒後に処理再開」と表示されます。

コンソールアプリで一時停止したいだけであれば、このようにThread.Sleepを使っても問題ないケースがあります。

ただし、同じコードをUIアプリのボタンクリックイベントなどで使うと、待機中に画面操作ができなくなるため注意が必要です。

3-5. Thread.Sleep(0)の意味と使いどころ

Thread.Sleep(0)は、指定時間だけ待つというよりも、現在のスレッドが持っている実行権をいったん手放すために使われます。

C#
Thread.Sleep(0);

これは「同じ優先度のほかのスレッドに実行機会を譲る」という意味合いがあります。待機時間として0を指定しているため、通常の意味でのスリープとは少し異なります。

ただし、アプリケーションコードでThread.Sleep(0)を積極的に使う場面は多くありません。スレッドスケジューリングに関わる低レベルな調整で使われることがありますが、通常はTask.Delayや適切な同期機構を使うほうが分かりやすく安全です。

3-6. Thread.Sleep(Timeout.Infinite)の注意点

Thread.Sleepには、Timeout.Infiniteを指定できます。

C#
Thread.Sleep(Timeout.Infinite);

これは無期限にスレッドを停止する指定です。つまり、基本的にはそのスレッドは再開されません。

この使い方は非常に注意が必要です。メインスレッドで実行すると、アプリケーションが終了しない、または処理が完全に止まったように見える原因になります。

無期限に待機したい場合でも、通常はキャンセル可能な仕組みやシグナル待機を使うべきです。

C#
var cancellationTokenSource = new CancellationTokenSource();

try
{
await Task.Delay(Timeout.Infinite, cancellationTokenSource.Token);
}
catch (TaskCanceledException)
{
Console.WriteLine("待機がキャンセルされました");
}

無限待機を使う場合は、必ず解除手段やキャンセル手段を用意しましょう。

4. Task.Delayの使い方とサンプルコード

4-1. Task.Delayの基本構文

Task.Delayの基本構文は次のとおりです。

C#
await Task.Delay(待機時間);

待機時間はミリ秒単位の整数、またはTimeSpanで指定できます。

C#
await Task.Delay(1000);
await Task.Delay(TimeSpan.FromSeconds(1));

Task.Delayを使うには、通常は非同期メソッドの中でawaitと組み合わせます。

C#
using System.Threading.Tasks;

Task.Delayは、指定時間が経過した後に完了するTaskを返します。Thread.Sleepのように現在のスレッドを停止するのではなく、非同期的に待機する点が大きな特徴です。

4-2. async/awaitと組み合わせて待機する方法

Task.Delayは、async/awaitと組み合わせて使うのが基本です。

C#
using System;
using System.Threading.Tasks;

class Program
{
static async Task Main()
{
Console.WriteLine("処理開始");

await Task.Delay(1000);

Console.WriteLine("1秒後に処理再開");
}
}

このコードでは、await Task.Delay(1000)によって1秒待機します。

awaitを使うことで、待機中にスレッドを占有せず、待機が完了したあとに続きの処理を再開できます。これは、UIアプリやWebアプリで特に重要です。

4-3. Task.Delay(1000)で1秒待機するサンプル

Task.Delay(1000)を使った1秒待機のサンプルです。

C#
using System;
using System.Threading.Tasks;

class Program
{
static async Task Main()
{
Console.WriteLine("開始");

await WaitOneSecondAsync();

Console.WriteLine("終了");
}

static async Task WaitOneSecondAsync()
{
await Task.Delay(1000);
Console.WriteLine("1秒待機しました");
}
}

Task.Delay(1000)だけを書くと、単にTaskが作られるだけで、待機完了を待たずに次の処理へ進んでしまう可能性があります。

正しく待機したい場合は、基本的にawaitを付けます。

C#
await Task.Delay(1000);

4-4. TimeSpanを使って待機時間を指定する方法

Task.Delayでも、TimeSpanを使って待機時間を指定できます。

C#
await Task.Delay(TimeSpan.FromSeconds(1));
await Task.Delay(TimeSpan.FromMilliseconds(500));
await Task.Delay(TimeSpan.FromMinutes(1));

TimeSpanを使うと、待機時間の単位が明確になります。

C#
await Task.Delay(TimeSpan.FromSeconds(5));

このコードは5秒待機します。

Task.Delay(5000)でも同じ意味ですが、TimeSpan.FromSeconds(5)のほうが読みやすく、意図が伝わりやすい場合があります。

4-5. CancellationTokenで待機をキャンセルする方法

Task.Delayは、CancellationTokenを使って待機をキャンセルできます。

C#
using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
static async Task Main()
{
using var cts = new CancellationTokenSource();

var task = WaitAsync(cts.Token);

cts.CancelAfter(1000);

await task;
}

static async Task WaitAsync(CancellationToken cancellationToken)
{
try
{
Console.WriteLine("5秒待機を開始します");

await Task.Delay(5000, cancellationToken);

Console.WriteLine("5秒待機しました");
}
catch (TaskCanceledException)
{
Console.WriteLine("待機がキャンセルされました");
}
}
}

この例では、本来5秒待機する処理を、1秒後にキャンセルしています。

長時間の待機や、ユーザー操作によって中断できる処理では、CancellationTokenを使うことで安全な設計にできます。

4-6. Task.Delayを使うときの注意点

Task.Delayを使うときに最も多いミスは、awaitを付け忘れることです。

C#
Task.Delay(1000);
Console.WriteLine("すぐに実行される");

このコードでは、Task.Delay(1000)の完了を待っていないため、次の行がすぐに実行されます。

正しくは次のように書きます。

C#
await Task.Delay(1000);
Console.WriteLine("1秒後に実行される");

また、Task.Delayを使うメソッドは基本的にasyncにする必要があります。

C#
static async Task SampleAsync()
{
await Task.Delay(1000);
}

非同期メソッド内でTask.Delay(...).Wait()のように同期的に待つのは避けましょう。スレッドブロックやデッドロックの原因になる可能性があります。

5. Thread.SleepとTask.Delayの違い

5-1. 同期処理と非同期処理の違い

Thread.SleepTask.Delayの大きな違いは、同期処理か非同期処理かです。

Thread.Sleepは同期的に待機します。

C#
Thread.Sleep(1000);

この間、現在のスレッドは停止し、次の処理には進みません。

一方、Task.Delayは非同期的に待機します。

C#
await Task.Delay(1000);

この場合、待機中にスレッドを占有せず、指定時間が経過したあとで続きの処理が再開されます。

同期処理の中で簡単に止めたいだけならThread.Sleepでも動きますが、非同期処理ではTask.Delayを使うのが基本です。

5-2. スレッドをブロックするかどうかの違い

Thread.Sleepはスレッドをブロックします。

C#
Thread.Sleep(3000);

この3秒間、そのスレッドは他の処理に使えません。

一方、Task.Delayはスレッドをブロックしません。

C#
await Task.Delay(3000);

待機中のスレッドは解放され、ほかの処理に使われる可能性があります。

この違いは、アプリケーションの応答性やスケーラビリティに大きく影響します。特にWebアプリやAPIでは、スレッドをブロックしないTask.Delayのほうが適しています。

5-3. UIアプリでの動作の違い

WinFormsやWPFなどのUIアプリでは、UIスレッドが画面描画やユーザー操作を担当しています。

そのUIスレッド上でThread.Sleepを使うと、画面が固まります。

C#
private void Button_Click(object sender, EventArgs e)
{
Thread.Sleep(3000);
label1.Text = "完了";
}

このコードでは、3秒間UIが反応しなくなります。

一方、Task.Delayを使うと、UIスレッドをブロックせずに待機できます。

C#
private async void Button_Click(object sender, EventArgs e)
{
await Task.Delay(3000);
label1.Text = "完了";
}

この場合、待機中も画面操作や描画が止まりにくくなります。UIアプリでC#のスリープ処理を書く場合は、原則としてTask.Delayを使いましょう。

5-4. Webアプリ・APIでの影響の違い

ASP.NETなどのWebアプリでThread.Sleepを使うと、リクエスト処理中のスレッドをブロックします。

C#
public IActionResult Index()
{
Thread.Sleep(1000);
return View();
}

このようなコードが大量に実行されると、サーバーのスレッドが不足し、応答性能が低下する可能性があります。

非同期アクションでは、Task.Delayを使うほうが適しています。

C#
public async Task<IActionResult> Index()
{
await Task.Delay(1000);
return View();
}

ただし、Webアプリで意味なく待機すること自体は避けるべきです。API制限やリトライなど明確な理由がある場合に限り、非同期で待機するようにしましょう。

5-5. パフォーマンスへの影響の違い

Thread.Sleepは、スレッドを占有したまま待機します。待機時間中、そのスレッドは何も処理できません。

コンソールアプリのように影響範囲が小さい場合は大きな問題にならないこともあります。しかし、サーバーアプリや多数の処理が並行するアプリでは、スレッドを無駄に占有することがパフォーマンス低下につながります。

Task.Delayは、待機中にスレッドを解放できるため、より効率的です。特に、多数の非同期処理を扱う場面ではTask.Delayのほうが向いています。

5-6. 例外処理・キャンセル処理の違い

Thread.Sleepは、基本的に指定時間が経過するまで待機します。キャンセルしたい場合は、別の設計が必要です。

一方、Task.DelayCancellationTokenを受け取れるため、待機をキャンセルしやすいという利点があります。

C#
await Task.Delay(5000, cancellationToken);

キャンセルされた場合は、TaskCanceledExceptionが発生します。

C#
try
{
await Task.Delay(5000, cancellationToken);
}
catch (TaskCanceledException)
{
Console.WriteLine("キャンセルされました");
}

ユーザー操作で中止できる処理や、アプリ終了時に停止したい処理では、Task.DelayCancellationTokenを組み合わせると安全です。

6. Thread.SleepとTask.Delayの使い分け

6-1. コンソールアプリで一時停止したい場合

コンソールアプリで単純に一時停止したい場合は、Thread.Sleepでも問題ないケースがあります。

C#
Console.WriteLine("3秒待機します");
Thread.Sleep(3000);
Console.WriteLine("再開しました");

処理が単純で、待機中にほかの処理を行う必要がない場合は、Thread.Sleepは分かりやすい選択です。

ただし、コンソールアプリでも非同期処理を使っている場合は、Task.Delayを使うほうが自然です。

C#
Console.WriteLine("3秒待機します");
await Task.Delay(3000);
Console.WriteLine("再開しました");

6-2. WinForms・WPFなどのUIアプリで待機したい場合

WinFormsやWPFなどのUIアプリでは、基本的にThread.Sleepを使わないようにしましょう。

悪い例は次のとおりです。

C#
private void Button_Click(object sender, EventArgs e)
{
Thread.Sleep(2000);
label1.Text = "完了";
}

このコードでは、2秒間画面が固まります。

正しくは、Task.Delayを使います。

C#
private async void Button_Click(object sender, EventArgs e)
{
await Task.Delay(2000);
label1.Text = "完了";
}

これにより、待機中もUIスレッドをブロックせず、アプリの応答性を保てます。

6-3. ASP.NETなどのWebアプリで待機したい場合

ASP.NETなどのWebアプリでは、Thread.Sleepを避け、必要な場合はTask.Delayを使います。

C#
public async Task<IActionResult> RetrySample()
{
await Task.Delay(1000);
return Ok("完了");
}

ただし、Webアプリで単純に待機時間を入れるのは、ユーザーのレスポンスを遅くするだけになる場合があります。

リトライ、レート制限、外部サービスの都合など、明確な理由がある場合に限定しましょう。また、本格的なリトライ処理では、指数バックオフやリトライライブラリの利用も検討すべきです。

6-4. 非同期処理の中で待機したい場合

非同期メソッドの中では、Thread.SleepではなくTask.Delayを使います。

悪い例です。

C#
static async Task SampleAsync()
{
Thread.Sleep(1000);
await SomeAsyncMethod();
}

このコードは、非同期メソッドであるにもかかわらず、スレッドをブロックしています。

良い例は次のとおりです。

C#
static async Task SampleAsync()
{
await Task.Delay(1000);
await SomeAsyncMethod();
}

非同期処理の中では、待機も非同期で行うのが基本です。

6-5. テストコードやデバッグで一時的に待機したい場合

テストコードやデバッグ中に、一時的に待機したい場合があります。

C#
Thread.Sleep(1000);

このような使い方は、動作確認のためであれば問題ない場合もあります。

ただし、自動テストでSleepに依存するのは避けるべきです。

C#
Thread.Sleep(3000);
Assert.True(result.IsCompleted);

このようなテストは、環境によって失敗したり、必要以上にテスト時間が長くなったりします。

可能であれば、条件が満たされるまで待つ仕組みや、イベント、モック、非同期の完了待ちを使いましょう。

6-6. 結局どちらを使うべきか

結論として、現代的なC#では、基本的にTask.Delayを優先して使うのがおすすめです。

特に次のような場面ではTask.Delayを使いましょう。

場面推奨
非同期処理Task.Delay
UIアプリTask.Delay
Webアプリ・APITask.Delay
キャンセル可能な待機Task.Delay
単純なコンソールアプリThread.Sleepでも可
一時的なデバッグThread.Sleepでも可

Thread.Sleepは非推奨というわけではありませんが、使いどころが限られます。スレッドをブロックしても問題ない場面でのみ使うと考えるとよいでしょう。

7. C#のスリープ処理でよくある失敗と対処法

7-1. UIが固まってしまう

C#のスリープ処理でよくある失敗が、UIスレッドでThread.Sleepを使ってしまうことです。

C#
private void Button_Click(object sender, EventArgs e)
{
Thread.Sleep(3000);
label1.Text = "完了";
}

このコードでは、3秒間UIスレッドが止まるため、画面が固まります。

対処法は、Task.Delayを使うことです。

C#
private async void Button_Click(object sender, EventArgs e)
{
await Task.Delay(3000);
label1.Text = "完了";
}

UIアプリでは、待機中もUIスレッドを解放することが重要です。

7-2. 非同期メソッド内でThread.Sleepを使ってしまう

非同期メソッドの中でThread.Sleepを使うと、せっかくの非同期処理のメリットが失われます。

C#
static async Task ExecuteAsync()
{
Thread.Sleep(1000);
await LoadDataAsync();
}

このコードでは、1秒間スレッドがブロックされます。

対処法は、Task.Delayに置き換えることです。

C#
static async Task ExecuteAsync()
{
await Task.Delay(1000);
await LoadDataAsync();
}

非同期メソッド内の待機は、原則としてawait Task.Delayを使いましょう。

7-3. Task.Delayにawaitを付け忘れる

Task.Delayでよくあるミスが、awaitの付け忘れです。

C#
Task.Delay(1000);
Console.WriteLine("すぐに実行される");

このコードでは、1秒待たずに次の処理が実行されます。

正しくは次のように書きます。

C#
await Task.Delay(1000);
Console.WriteLine("1秒後に実行される");

Task.Delayは、呼び出しただけでは処理の流れを止めません。待機完了を待つには、awaitが必要です。

7-4. スリープ時間が正確ではない

Thread.Sleep(1000)Task.Delay(1000)は、正確に1000ミリ秒後に必ず再開されるわけではありません。

OSのスケジューリング、CPU負荷、スレッドプールの状態などによって、再開タイミングは多少ずれることがあります。

そのため、厳密なタイミング制御が必要な処理では、単純なスリープに頼るべきではありません。経過時間を測る場合は、Stopwatchを使うなど、目的に合った方法を選びましょう。

C#
var stopwatch = System.Diagnostics.Stopwatch.StartNew();

await Task.Delay(1000);

Console.WriteLine(stopwatch.ElapsedMilliseconds);

スリープ時間は「少なくともその程度待つ」ものとして扱うのが安全です。

7-5. 無限待機や長時間待機で処理が止まる

Thread.Sleep(Timeout.Infinite)や非常に長い待機時間を指定すると、処理が止まったように見えることがあります。

C#
Thread.Sleep(Timeout.Infinite);

このようなコードは、解除手段がないと危険です。

長時間待機が必要な場合は、キャンセル可能なTask.Delayを検討しましょう。

C#
await Task.Delay(TimeSpan.FromMinutes(10), cancellationToken);

キャンセルできる設計にしておくことで、アプリ終了時やユーザー操作に対応しやすくなります。

7-6. スリープで処理順序を無理に制御してしまう

処理順序を調整するためにスリープを使うのは避けるべきです。

悪い例です。

C#
StartProcess();

Thread.Sleep(2000);

ReadResult();

このコードは「2秒後には処理が終わっているはず」という不確実な前提に依存しています。

正しくは、処理完了を待つ仕組みを使います。

C#
await StartProcessAsync();

ReadResult();

または、イベントやコールバックを使って完了を通知する設計にします。

スリープでタイミングを調整するコードは、環境が変わると壊れやすいため注意しましょう。

8. スリープ処理を避けたほうがよいケース

8-1. 処理完了待ちにスリープを使うケース

処理の完了を待つ目的でスリープを使うのは避けるべきです。

C#
StartDownload();

Thread.Sleep(5000);

OpenFile();

このコードでは、5秒後にダウンロードが完了している保証がありません。通信環境やファイルサイズによっては、まだ完了していない可能性があります。

処理完了を待つなら、非同期メソッドの完了をawaitするべきです。

C#
await DownloadAsync();

OpenFile();

「時間を待つ」のではなく、「完了を待つ」設計にすることが重要です。

8-2. リトライ処理を単純なSleepだけで実装するケース

リトライ処理でスリープを使うこと自体はありますが、単純に固定時間だけ待つ実装は不十分な場合があります。

C#
for (int i = 0; i < 3; i++)
{
try
{
await CallApiAsync();
break;
}
catch
{
await Task.Delay(1000);
}
}

このようなコードは簡単ですが、失敗理由の判定、最大リトライ回数、ログ出力、指数バックオフなどが不足しがちです。

実務では、失敗時に待機時間を段階的に増やす指数バックオフや、リトライ対象の例外を限定する設計が必要になることがあります。

8-3. マルチスレッドの同期制御に使うケース

マルチスレッド処理で、タイミング調整のためにThread.Sleepを使うのは危険です。

C#
sharedValue = 1;

Thread.Sleep(100);

Console.WriteLine(sharedValue);

スレッド間の同期を時間で制御すると、実行環境によって動作が不安定になります。

同期制御には、lockSemaphoreSlimMonitorManualResetEventSlimなど、目的に合った同期機構を使いましょう。

C#
lock (lockObject)
{
sharedValue++;
}

スリープは同期制御の代わりにはなりません。

8-4. UI更新のタイミング調整に使うケース

UI更新のタイミングを調整するためにThread.Sleepを使うと、画面が固まる原因になります。

C#
label1.Text = "処理中";
Thread.Sleep(1000);
label1.Text = "完了";

このコードでは、UIの描画が期待どおりに更新されないことがあります。

UIで一定時間表示を変えたい場合は、Task.Delayを使います。

C#
label1.Text = "処理中";
await Task.Delay(1000);
label1.Text = "完了";

また、定期的なUI更新には、UIフレームワークに適したTimerを使う方法もあります。

8-5. スリープの代替手段を検討すべきケース

次のような場合は、単純なスリープではなく代替手段を検討しましょう。

処理完了を待ちたい場合は、await、イベント、コールバックを使います。

一定間隔で処理したい場合は、Timerやバックグラウンドサービスを使います。

排他制御をしたい場合は、lockSemaphoreSlimを使います。

キャンセル可能な待機が必要な場合は、CancellationTokenを使います。

リトライ処理を実装したい場合は、指数バックオフやPollyなどのライブラリを検討します。

スリープは便利ですが、目的によってはより適切な手段があります。

9. スリープ処理の代替手段

9-1. Timerを使う方法

一定時間後に処理を実行したい場合や、一定間隔で繰り返したい場合は、Timerが適しています。

C#
using System;
using System.Threading;

class Program
{
static void Main()
{
using var timer = new Timer(_ =>
{
Console.WriteLine("定期実行");
}, null, 1000, 1000);

Console.ReadLine();
}
}

このコードでは、1秒後に処理を開始し、その後1秒ごとに処理を繰り返します。

単に現在の処理を止めるのではなく、指定時間後に別処理を実行したい場合は、Thread.SleepよりTimerのほうが適しています。

9-2. CancellationTokenを使う方法

長時間待機やキャンセル可能な処理では、CancellationTokenを使うと安全です。

C#
using var cts = new CancellationTokenSource();

try
{
await Task.Delay(TimeSpan.FromSeconds(10), cts.Token);
}
catch (TaskCanceledException)
{
Console.WriteLine("キャンセルされました");
}

ユーザーがキャンセルボタンを押した場合や、アプリケーション終了時に処理を中断したい場合に便利です。

Thread.Sleepでは待機を自然にキャンセルするのが難しいため、キャンセル可能な待機にはTask.DelayCancellationTokenを組み合わせるのが基本です。

9-3. SemaphoreSlimやlockで同期制御する方法

マルチスレッドや非同期処理で同時実行数を制御したい場合は、スリープではなくSemaphoreSlimlockを使います。

同期コードで排他制御する場合は、lockを使います。

C#
private readonly object _lock = new object();

void Update()
{
lock (_lock)
{
// 同時に実行されたくない処理
}
}

非同期処理で同時実行数を制御する場合は、SemaphoreSlimが便利です。

C#
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);

async Task UpdateAsync()
{
await _semaphore.WaitAsync();

try
{
// 同時に実行されたくない非同期処理
}
finally
{
_semaphore.Release();
}
}

同期制御を時間で解決しようとせず、適切な同期機構を使うことが重要です。

9-4. イベントやコールバックで処理完了を待つ方法

別処理の完了を待ちたい場合は、スリープではなくイベントやコールバックを使う方法があります。

たとえば、完了時にイベントを発火する設計にすれば、固定時間待機に依存せずに済みます。

C#
public event EventHandler? Completed;

void Finish()
{
Completed?.Invoke(this, EventArgs.Empty);
}

非同期処理であれば、Taskを返す設計にしてawaitするのが自然です。

C#
await DoWorkAsync();
Console.WriteLine("処理が完了しました");

「何秒待つか」ではなく「完了したら次に進む」という設計にすると、安定したコードになります。

9-5. Pollyなどでリトライ処理を実装する方法

リトライ処理では、単純なTask.Delayだけでなく、リトライ用ライブラリを使う方法もあります。

C#では、Pollyのようなライブラリを使うことで、リトライ、待機、タイムアウト、サーキットブレーカーなどを整理して実装できます。

考え方としては、次のような機能を組み合わせます。

C#
// イメージ
// 失敗したら待機して再試行
// 待機時間は回数に応じて増やす
// 特定の例外だけリトライ対象にする

外部APIやネットワーク処理では、一時的な失敗が発生することがあります。そのような場合、固定のSleepだけで対応するよりも、リトライポリシーとして設計するほうが保守しやすくなります。

10. C#のスリープ処理に関するよくある質問

10-1. C#で1秒待つにはどう書けばよいか

C#で1秒待つには、同期処理ならThread.Sleep(1000)を使います。

C#
Thread.Sleep(1000);

非同期処理なら、Task.Delay(1000)awaitします。

C#
await Task.Delay(1000);

UIアプリやWebアプリ、非同期メソッドの中では、基本的にawait Task.Delay(1000)を使うのがおすすめです。

10-2. Thread.SleepとTask.Delayはどちらが推奨されるか

多くのアプリケーションでは、Task.Delayが推奨されます。

理由は、Task.Delayがスレッドをブロックせず、非同期処理と相性がよいからです。

ただし、Thread.Sleepが不要というわけではありません。コンソールアプリで単純に待機したい場合や、デバッグ目的で一時的に止めたい場合には使えます。

判断基準は、「スレッドを止めてもよいか」です。止めても問題ないならThread.Sleep、止めたくないならTask.Delayを使います。

10-3. Thread.Sleepは非推奨なのか

Thread.Sleep自体が完全に非推奨というわけではありません。

ただし、使う場面には注意が必要です。UIスレッドやWebアプリのリクエスト処理、非同期メソッドの中で使うと問題になることがあります。

Thread.Sleepは「現在のスレッドをブロックする」という性質を理解したうえで、影響が限定的な場面で使いましょう。

現代的なC#では、非同期処理にはTask.Delayを使うのが基本です。

10-4. Task.Delayが効かない原因は何か

Task.Delayが効かないように見える原因として多いのは、awaitを付け忘れているケースです。

C#
Task.Delay(1000);

このコードだけでは、待機完了を待たずに次の処理へ進んでしまいます。

正しくは次のように書きます。

C#
await Task.Delay(1000);

また、呼び出し元のメソッドがasyncになっていない場合も注意が必要です。

C#
static async Task SampleAsync()
{
await Task.Delay(1000);
}

Task.Delayは非同期処理の仕組みとセットで使うものだと考えましょう。

10-5. Sleepの待機時間は正確なのか

Thread.SleepTask.Delayの待機時間は、厳密に正確ではありません。

たとえば、Thread.Sleep(1000)と書いても、必ず正確に1000ミリ秒後に再開されるとは限りません。OSのスケジューリングや実行環境の負荷によって、再開タイミングは前後します。

そのため、厳密な時間計測やリアルタイム性が必要な処理では、スリープだけに頼るべきではありません。

経過時間を測りたい場合は、Stopwatchを使うなど、目的に応じた方法を選びましょう。

10-6. Unityでスリープ処理を使う場合はどうすべきか

UnityでThread.Sleepを使うと、メインスレッドが停止し、ゲーム画面や入力処理が止まる原因になります。

Unityで一定時間待ちたい場合は、通常はコルーチンを使います。

C#
using System.Collections;
using UnityEngine;

public class Sample : MonoBehaviour
{
IEnumerator Start()
{
Debug.Log("開始");

yield return new WaitForSeconds(1f);

Debug.Log("1秒後");
}
}

Unityでは、ゲームループを止めずに待機することが重要です。通常のC#アプリと同じ感覚でThread.Sleepを使わないようにしましょう。

11. C#でスリープ処理を安全に使うためのベストプラクティス

11-1. 基本はTask.Delayを優先する

C#でスリープ処理を書く場合、基本的にはTask.Delayを優先して検討しましょう。

C#
await Task.Delay(1000);

特に、非同期処理、UIアプリ、WebアプリではTask.Delayが適しています。

Thread.Sleepはシンプルですが、スレッドをブロックするため、使いどころを間違えるとパフォーマンスや応答性に悪影響を与えます。

11-2. UIスレッドをブロックしない

UIアプリでは、UIスレッドをブロックしないことが重要です。

避けるべきコードです。

C#
Thread.Sleep(1000);

UIイベント内では、次のように書きます。

C#
await Task.Delay(1000);

これにより、待機中も画面描画やユーザー操作を妨げにくくなります。

C#のスリープ処理で画面が固まる場合は、まずThread.Sleepを使っていないか確認しましょう。

11-3. キャンセル可能な待機処理にする

長時間の待機や繰り返し処理では、キャンセル可能にしておくと安全です。

C#
await Task.Delay(TimeSpan.FromSeconds(30), cancellationToken);

キャンセル可能にしておくと、ユーザー操作、タイムアウト、アプリ終了などに対応しやすくなります。

特にバックグラウンド処理やサーバー処理では、キャンセルを考慮した設計が重要です。

11-4. 固定時間待機に依存しすぎない

固定時間のスリープに依存しすぎると、不安定なコードになりやすいです。

C#
await Task.Delay(3000);

このコード自体は問題ありませんが、「3秒待てば必ず準備が終わる」といった前提で使うのは危険です。

処理完了を待つなら、完了通知やawaitを使いましょう。

C#
await InitializeAsync();

スリープは時間調整には使えますが、処理の正確な同期には向いていません。

11-5. 用途に応じて代替手段を選ぶ

C#のスリープ処理では、用途に応じて適切な方法を選ぶことが大切です。

単純に一時停止したいなら、Thread.SleepまたはTask.Delayを使います。

非同期処理で待ちたいなら、Task.Delayを使います。

一定間隔で処理したいなら、Timerを使います。

処理完了を待ちたいなら、await、イベント、コールバックを使います。

同時実行を制御したいなら、lockSemaphoreSlimを使います。

リトライ処理を実装したいなら、指数バックオフやPollyなどの仕組みを検討します。

スリープ処理だけで解決しようとせず、目的に合った待機・同期方法を選ぶことが、保守しやすいC#コードにつながります。

まとめ

C#でスリープ処理を実装する方法には、主にThread.SleepTask.Delayがあります。

Thread.Sleepは、現在のスレッドを指定時間ブロックする同期的な待機方法です。コンソールアプリや一時的なデバッグ用途では使いやすい一方で、UIアプリやWebアプリ、非同期処理では問題を起こしやすいため注意が必要です。

一方、Task.Delayは、スレッドをブロックせずに待機できる非同期的な方法です。async/awaitと組み合わせて使うことで、UIの応答性やWebアプリのスケーラビリティを保ちやすくなります。

C#で1秒待つだけなら、同期処理では次のように書けます。

C#
Thread.Sleep(1000);

非同期処理では、次のように書きます。

C#
await Task.Delay(1000);

実務では、基本的にTask.Delayを優先し、Thread.Sleepはスレッドをブロックしても問題ない限定的な場面で使うのが安全です。

また、処理完了待ち、リトライ、同期制御、UI更新などをすべてスリープで解決しようとするのは避けましょう。TimerCancellationTokenSemaphoreSlim、イベント、コールバック、リトライライブラリなど、目的に合った代替手段を選ぶことが重要です。

C#のスリープ処理はシンプルですが、使い方を間違えると不具合やパフォーマンス低下の原因になります。Thread.SleepTask.Delayの違いを理解し、アプリケーションの種類や処理内容に合わせて正しく使い分けましょう。