C#で待機する方法を完全解説|Thread.SleepとTask.Delayの違い・使い分けと実装例

はじめに

C#で一定時間待機したい場合、代表的な方法はThread.SleepTask.Delayです。

結論から言うと、現在のC#開発では多くの場面でTask.Delayを使うのが基本です。Thread.Sleepは現在のスレッドを完全に止める同期的な待機方法であり、UIアプリやWebアプリでは画面のフリーズやパフォーマンス低下につながることがあります。

一方、Task.Delayは非同期で待機できるため、待機中もスレッドを占有しにくく、UIアプリ、ASP.NET、非同期処理との相性が良い方法です。

この記事では、C#で待機する方法について、Thread.SleepTask.Delayの違い、使い分け、実装例、よくある失敗まで詳しく解説します。

1. C#で待機する方法の結論|まず何を使えばいいのか

1-1. C#で一定時間待機する主な方法

C#で待機処理を実装する主な方法は次のとおりです。

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

Thread.Sleepは現在実行中のスレッドを指定時間停止します。
Task.Delayは指定時間後に完了するタスクを作成し、awaitすることで非同期に待機できます。

1-2. 基本はTask.Delayを使うべき理由

基本的にはTask.Delayを使うのがおすすめです。

C#
await Task.Delay(1000);

Task.Delayはスレッドをブロックしないため、待機中に他の処理へスレッドを解放できます。特に、Windows Forms、WPF、ASP.NET、非同期メソッド内ではTask.Delayが適しています。

1-3. Thread.Sleepを使ってよいケース

Thread.Sleepを使ってよいのは、主に次のようなケースです。

C#
Thread.Sleep(1000);

コンソールアプリで簡単に処理を止めたい場合、テスト用に一時的な待機を入れたい場合、専用のバックグラウンドスレッドを明示的に停止したい場合などです。

ただし、UIスレッドやASP.NETのリクエスト処理では基本的に避けるべきです。

1-4. 待機処理を選ぶときの判断基準

C#で待機処理を選ぶ基準はシンプルです。

非同期処理の中ではTask.Delayを使います。

C#
await Task.Delay(1000);

同期処理で、現在のスレッドを止めても問題ない場合だけThread.Sleepを使います。

C#
Thread.Sleep(1000);

迷った場合は、まずTask.Delayを選ぶとよいでしょう。

2. C#のThread.Sleepとは|スレッドを止めて待機する方法

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

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

C#
using System.Threading;

Thread.Sleep(1000);

引数にはミリ秒を指定します。1000を指定すると1秒待機します。

2-2. ミリ秒・秒・分を指定して待機する実装例

500ミリ秒待機する例です。

C#
Thread.Sleep(500);

1秒待機する例です。

C#
Thread.Sleep(1000);

5秒待機する例です。

C#
Thread.Sleep(5000);

1分待機する例です。

C#
Thread.Sleep(60 * 1000);

TimeSpanを使って書くこともできます。

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

TimeSpanを使うと、待機時間の意図が読み取りやすくなります。

2-3. Thread.Sleepで現在のスレッドがブロックされる仕組み

Thread.Sleepを呼び出すと、そのコードを実行している現在のスレッドが指定時間停止します。

C#
Console.WriteLine("開始");
Thread.Sleep(3000);
Console.WriteLine("3秒後に表示");

この場合、Thread.Sleep(3000)の間、現在のスレッドは他の処理を実行できません。

UIスレッドで実行すれば画面が固まり、Webアプリのリクエスト処理で実行すれば、そのリクエストを処理しているスレッドが無駄に占有されます。

2-4. Thread.Sleepを使うメリット

Thread.Sleepのメリットは、書き方が非常に簡単なことです。

C#
Thread.Sleep(1000);

同期処理の中で簡単に待機でき、asyncawaitを使わなくても実装できます。

また、コンソールアプリや簡単な検証コードでは直感的に使えます。

2-5. Thread.Sleepを使うデメリットと注意点

Thread.Sleepの最大のデメリットは、スレッドをブロックすることです。

C#
Thread.Sleep(10000);

このように10秒待機すると、そのスレッドは10秒間何もできません。

特に次の場面では注意が必要です。

UIアプリで使うと画面が固まります。
ASP.NETで使うとサーバーリソースを無駄に消費します。
非同期処理の中で使うと、非同期の利点を失います。
正確なタイマー用途には向いていません。

3. C#のTask.Delayとは|非同期で待機する方法

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

Task.Delayは、指定時間後に完了するタスクを返すメソッドです。

C#
await Task.Delay(1000);

awaitと組み合わせることで、非同期に1秒待機できます。

使用するメソッドにはasyncを付けます。

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

3-2. await Task.Delayで指定時間待つ実装例

1秒待機する例です。

C#
public async Task WaitOneSecondAsync()
{
Console.WriteLine("開始");

await Task.Delay(1000);

Console.WriteLine("1秒後");
}

await Task.Delay(1000)の間、現在のスレッドを占有し続けません。

3-3. TimeSpanを使って秒・分単位で待機する方法

Task.DelayでもTimeSpanを使えます。

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

1分待機する場合は次のように書けます。

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

ミリ秒の数値を直接書くよりも、TimeSpan.FromSecondsTimeSpan.FromMinutesを使った方が意図が明確です。

3-4. Task.Delayがスレッドをブロックしない仕組み

Task.Delayは、待機中にスレッドを停止して占有するわけではありません。

C#
await Task.Delay(3000);

このコードでは、3秒後に処理を再開する予約を行い、それまでスレッドを解放できます。そのため、UIアプリでは画面の応答性を保ちやすく、Webアプリではサーバーのスレッドを効率よく使えます。

3-5. Task.Delayを使うメリットと注意点

Task.Delayのメリットは、非同期で待機できることです。

C#
await Task.Delay(1000);

UIを固めにくく、Webアプリでもスレッドを無駄に占有しにくく、キャンセル処理とも組み合わせやすいです。

ただし、awaitを忘れると期待どおりに待機しません。

C#
Task.Delay(1000); // 待機されない

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

C#
await Task.Delay(1000);

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

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

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

C#
Thread.Sleep(1000);

Task.Delayはスレッドをブロックせずに待機できます。

C#
await Task.Delay(1000);

この違いが、両者を使い分けるうえで最も重要です。

4-2. 同期処理と非同期処理での違い

Thread.Sleepは同期的な待機です。

C#
Console.WriteLine("開始");
Thread.Sleep(1000);
Console.WriteLine("終了");

Task.Delayは非同期処理で使う待機です。

C#
Console.WriteLine("開始");
await Task.Delay(1000);
Console.WriteLine("終了");

非同期メソッド内では、基本的にTask.Delayを使います。

4-3. UIアプリで画面が固まるかどうかの違い

Windows FormsやWPFでUIスレッド上からThread.Sleepを呼ぶと、画面が固まります。

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

このコードでは3秒間UIが操作できなくなります。

一方、Task.Delayを使えばUIの応答性を保てます。

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

4-4. Webアプリ・APIでのパフォーマンスへの影響

ASP.NETでThread.Sleepを使うと、リクエスト処理中のスレッドを止めてしまいます。

C#
Thread.Sleep(1000);

大量アクセスがある環境では、スレッドが無駄に消費され、パフォーマンス低下につながります。

非同期処理ではTask.Delayを使います。

C#
await Task.Delay(1000);

ただし、本番環境のAPIで意味のない待機を入れるべきではありません。待機が必要なのは、リトライ、レート制限、ポーリング、バックグラウンド処理など明確な理由がある場合です。

4-5. Thread.SleepとTask.Delayの比較表

項目Thread.SleepTask.Delay
待機方式同期非同期
スレッドブロックするブロックしにくい
async/await不要基本的に必要
UIアプリ固まりやすい固まりにくい
Webアプリ非推奨推奨されやすい
キャンセル扱いにくいCancellationToken対応
主な用途簡易的な同期待機非同期処理の待機

4-6. どちらを使うべきかケース別に解説

コンソールアプリで単純に止めたいだけなら、Thread.Sleepでも問題ない場合があります。

C#
Thread.Sleep(1000);

非同期メソッド内ではTask.Delayを使います。

C#
await Task.Delay(1000);

UIアプリではTask.Delayを使います。

C#
await Task.Delay(1000);

ASP.NETやAPIでは、必要な場合のみTask.Delayを使います。

C#
await Task.Delay(1000);

5. 用途別|C#で待機処理を実装するサンプルコード

5-1. コンソールアプリで1秒待機する

同期的に1秒待機する例です。

C#
using System;
using System.Threading;

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

Thread.Sleep(1000);

Console.WriteLine("1秒後に終了");
}
}

非同期で書く場合は次のようにします。

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

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

await Task.Delay(1000);

Console.WriteLine("1秒後に終了");
}
}

5-2. for文・while文のループ内で一定間隔待機する

for文で1秒ごとに処理する例です。

C#
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"{i + 1}回目");

await Task.Delay(1000);
}

while文で一定間隔ごとに処理する例です。

C#
while (true)
{
Console.WriteLine("処理を実行");

await Task.Delay(TimeSpan.FromSeconds(5));
}

無限ループで使う場合は、キャンセルできる設計にするのが重要です。

5-3. ボタンクリック後に一定時間待つ

Windows Formsのボタンクリックで待機する例です。

C#
private async void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
label1.Text = "処理中...";

await Task.Delay(3000);

label1.Text = "完了";
button1.Enabled = true;
}

このようにTask.Delayを使えば、待機中も画面が固まりにくくなります。

5-4. 非同期処理の途中で待機する

非同期処理の途中で待機する例です。

C#
public async Task ExecuteAsync()
{
Console.WriteLine("処理1");

await Task.Delay(1000);

Console.WriteLine("処理2");

await Task.Delay(1000);

Console.WriteLine("処理3");
}

非同期メソッド内での待機は、Thread.SleepではなくTask.Delayを使うのが基本です。

5-5. 複数タスクを並行実行しながら待機する

複数のタスクを並行実行する例です。

C#
var task1 = Task.Run(async () =>
{
await Task.Delay(1000);
Console.WriteLine("タスク1完了");
});

var task2 = Task.Run(async () =>
{
await Task.Delay(2000);
Console.WriteLine("タスク2完了");
});

await Task.WhenAll(task1, task2);

Task.Delayを使うことで、複数の非同期処理を効率よく待機できます。

5-6. キャンセル可能な待機処理を実装する

CancellationTokenを使うと、待機中の処理をキャンセルできます。

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

public async Task WaitWithCancelAsync(CancellationToken cancellationToken)
{
try
{
await Task.Delay(10000, cancellationToken);
Console.WriteLine("待機完了");
}
catch (TaskCanceledException)
{
Console.WriteLine("待機がキャンセルされました");
}
}

キャンセル可能な待機処理では、Task.Delayが便利です。

6. UIアプリで待機するときの注意点

6-1. Windows FormsやWPFでThread.Sleepを使うと画面が固まる理由

Windows FormsやWPFでは、UIの描画やクリック操作の処理は主にUIスレッドで行われます。

そのUIスレッドでThread.Sleepを実行すると、画面更新や操作受付が止まります。

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

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

6-2. UIを止めずに待機するTask.Delayの使い方

UIを止めずに待機したい場合は、Task.Delayを使います。

C#
private async void button1_Click(object sender, EventArgs e)
{
label1.Text = "待機中...";

await Task.Delay(5000);

label1.Text = "完了";
}

await Task.Delayを使うことで、待機中もUIスレッドを占有し続けずに済みます。

6-3. async voidイベントハンドラで待機する実装例

イベントハンドラではasync voidを使うことがあります。

C#
private async void buttonStart_Click(object sender, EventArgs e)
{
buttonStart.Enabled = false;

await Task.Delay(TimeSpan.FromSeconds(3));

buttonStart.Enabled = true;
}

通常のメソッドではasync Taskが望ましいですが、UIイベントハンドラではasync voidが使われます。

6-4. 待機後にUIを更新する方法

await Task.Delayの後にUIを更新できます。

C#
private async void button1_Click(object sender, EventArgs e)
{
statusLabel.Text = "処理中";

await Task.Delay(2000);

statusLabel.Text = "完了";
}

多くの場合、await後は元のUIコンテキストに戻るため、そのままUIコントロールを更新できます。

6-5. ローディング表示やボタン連打防止への活用例

ボタン連打防止の例です。

C#
private async void buttonSubmit_Click(object sender, EventArgs e)
{
buttonSubmit.Enabled = false;
loadingLabel.Text = "送信中...";

await Task.Delay(1000);

loadingLabel.Text = "送信完了";
buttonSubmit.Enabled = true;
}

実際のアプリでは、Task.Delayだけで処理時間を作るのではなく、API呼び出しや保存処理など本来の非同期処理と組み合わせて使います。

7. Webアプリ・ASP.NETで待機するときの注意点

7-1. ASP.NETでThread.Sleepを避けるべき理由

ASP.NETでThread.Sleepを使うと、リクエストを処理しているスレッドを停止させます。

C#
Thread.Sleep(3000);

この間、そのスレッドは他のリクエスト処理に使えません。アクセス数が増えると、スレッド不足や応答遅延の原因になります。

7-2. Task.Delayでリクエスト処理を待機する実装例

ASP.NET CoreのAPIで待機する例です。

C#
[HttpGet]
public async Task<IActionResult> Get()
{
await Task.Delay(1000);

return Ok("完了");
}

このように非同期で待機すれば、Thread.Sleepよりもスレッドを効率よく扱えます。

ただし、意味のない待機をAPIに入れるべきではありません。

7-3. タイムアウト・リトライ処理で待機を使う方法

リトライ処理では、失敗後に少し待ってから再試行することがあります。

C#
for (int retry = 0; retry < 3; retry++)
{
try
{
await ExecuteAsync();
break;
}
catch
{
await Task.Delay(TimeSpan.FromSeconds(2));
}
}

実運用では、例外の種類を絞り、リトライ回数や待機時間を適切に設計する必要があります。

7-4. 本番環境で不要な待機処理を入れてはいけない理由

本番環境のWebアプリで不要な待機を入れると、レスポンスが遅くなります。

C#
await Task.Delay(5000);

テスト目的で入れた待機処理を消し忘れると、ユーザー体験やサーバー負荷に悪影響が出ます。

待機処理は、リトライ、レート制限、外部APIの制御、バックグラウンド処理など、明確な理由がある場合に限定しましょう。

7-5. BackgroundServiceや定期処理での待機方法

ASP.NET CoreのBackgroundServiceでは、Task.DelayCancellationTokenを組み合わせます。

C#
public class Worker : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
Console.WriteLine("定期処理を実行");

await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
}
}

サービス停止時にキャンセルできるよう、CancellationTokenを渡すのが重要です。

8. Thread.SleepやTask.Delayでよくある失敗

8-1. Task.Delayを書いただけでawaitし忘れる

よくあるミスが、Task.Delayを書いただけでawaitしないことです。

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

このコードでは、期待したように1秒待ってから次の処理に進むわけではありません。

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

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

8-2. Task.Delay().Wait()やResultでデッドロックを起こす

非同期処理を無理に同期的に待つと、デッドロックやパフォーマンス低下の原因になります。

C#
Task.Delay(1000).Wait();
C#
Task.Delay(1000).GetAwaiter().GetResult();

可能な限り、awaitを使って非同期のまま待機しましょう。

C#
await Task.Delay(1000);

8-3. Thread.Sleepで非同期処理まで止めてしまう

非同期メソッド内でThread.Sleepを使うのは避けるべきです。

C#
public async Task SampleAsync()
{
Thread.Sleep(1000);

await SomeAsync();
}

この場合、非同期メソッドでありながらスレッドをブロックしてしまいます。

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

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

await SomeAsync();
}

8-4. ミリ秒指定を間違えて待機時間が長すぎる

Thread.SleepTask.Delayの数値はミリ秒です。

C#
await Task.Delay(60);

これは60秒ではなく、60ミリ秒です。

60秒待ちたい場合は次のようにします。

C#
await Task.Delay(60 * 1000);

または、より読みやすく次のように書きます。

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

8-5. 正確なタイマー用途にSleepやDelayを使ってしまう

Thread.SleepTask.Delayは、厳密なリアルタイム制御には向いていません。

OSのスケジューリングや負荷状況によって、指定時間ぴったりに再開されるとは限りません。

経過時間を測定したい場合はStopwatchを使います。

C#
var stopwatch = Stopwatch.StartNew();

await Task.Delay(1000);

stopwatch.Stop();

Console.WriteLine(stopwatch.ElapsedMilliseconds);

9. 待機処理をより実用的にする関連機能

9-1. CancellationTokenで待機をキャンセルする

Task.DelayCancellationTokenを受け取れます。

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

キャンセル時の例です。

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

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

9-2. PeriodicTimerで定期的に処理を実行する

定期実行にはPeriodicTimerも使えます。

C#
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(5));

while (await timer.WaitForNextTickAsync())
{
Console.WriteLine("5秒ごとに実行");
}

whileTask.Delayで定期処理を書くより、定期実行の意図が明確になります。

9-3. Timer系クラスで一定間隔の処理を実装する

C#には複数のTimer系クラスがあります。

C#
var timer = new System.Timers.Timer(1000);
timer.Elapsed += (sender, e) =>
{
Console.WriteLine("1秒ごとに実行");
};
timer.Start();

一定間隔で繰り返し処理をしたい場合は、Thread.Sleepでループを止めるより、Timer系クラスを検討するとよいでしょう。

9-4. Task.WhenAnyでタイムアウト処理を実装する

Task.WhenAnyを使うと、処理本体とタイムアウトを競争させることができます。

C#
var task = LongRunningAsync();
var timeout = Task.Delay(TimeSpan.FromSeconds(5));

var completed = await Task.WhenAny(task, timeout);

if (completed == timeout)
{
Console.WriteLine("タイムアウト");
}
else
{
await task;
Console.WriteLine("処理完了");
}

外部API呼び出しや長時間処理のタイムアウト制御に使えます。

9-5. Stopwatchで経過時間を測定する

待機時間や処理時間を測定するにはStopwatchを使います。

C#
using System.Diagnostics;

var stopwatch = Stopwatch.StartNew();

await Task.Delay(1500);

stopwatch.Stop();

Console.WriteLine($"経過時間: {stopwatch.ElapsedMilliseconds}ms");

Thread.SleepTask.Delayは待機するための機能であり、正確な計測にはStopwatchが適しています。

10. C#の待機処理に関するよくある質問

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

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

C#
await Task.Delay(1000);

同期処理では次のように書けます。

C#
Thread.Sleep(1000);

基本的にはTask.Delayを使うのがおすすめです。

10-2. Thread.SleepとTask.Delayはどちらが正しい?

多くの場合、Task.Delayが適しています。

C#
await Task.Delay(1000);

Thread.Sleepは現在のスレッドを止めても問題ない限定的な場面で使います。

C#
Thread.Sleep(1000);

10-3. Task.DelayはCPUを使い続ける?

Task.Delayは、待機中にCPUを使い続ける処理ではありません。

C#
await Task.Delay(1000);

指定時間後に処理を再開するための非同期的な待機であり、ビジーループのようにCPUを消費し続けるものではありません。

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

Thread.Sleep自体が完全に使ってはいけない機能というわけではありません。

ただし、UIアプリやWebアプリ、非同期処理の中では避けるべき場面が多いです。

単純なコンソールアプリやテストコードなど、スレッドを止めても問題ない場合に限定して使うのがよいでしょう。

10-5. ミリ秒より細かい待機はできる?

Thread.SleepTask.Delayは基本的にミリ秒単位で指定します。

C#
await Task.Delay(1);

ただし、1ミリ秒を指定しても、実際にぴったり1ミリ秒後に再開されるとは限りません。

高精度な制御や計測が必要な場合は、用途に応じて別の仕組みを検討する必要があります。

10-6. Unityで待機する場合はどうする?

Unityでは、一般的なC#アプリとは異なり、コルーチンで待機することがよくあります。

C#
IEnumerator Sample()
{
Debug.Log("開始");

yield return new WaitForSeconds(1f);

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

UnityでThread.Sleepを使うとメインスレッドを止めてしまい、ゲーム画面が固まる原因になります。UnityではWaitForSecondsasync/await、UniTaskなど、用途に合った待機方法を選びましょう。

まとめ

C#で待機する方法には、主にThread.SleepTask.Delayがあります。

Thread.Sleepは現在のスレッドをブロックして待機する方法です。

C#
Thread.Sleep(1000);

一方、Task.Delayは非同期で待機する方法です。

C#
await Task.Delay(1000);

現在のC#開発では、基本的にTask.Delayを使うのがおすすめです。特に、UIアプリ、ASP.NET、非同期処理ではTask.Delayを選ぶべきです。

Thread.Sleepは、コンソールアプリや簡単な検証コードなど、現在のスレッドを止めても問題ない場面に限定して使いましょう。

待機処理を実装するときは、単に「何秒待つか」だけでなく、スレッドをブロックしてよいのか、UIやWebアプリに影響しないか、キャンセルできる必要があるかを考えることが重要です。