C#のSleepとDelayの違いとは?Thread.Sleep・Task.Delayの使い分けをサンプルコードで解説

はじめに

C#で「一定時間待つ」「処理を少し遅らせる」「数秒後に処理を再開する」といった実装をしたいとき、よく使われるのがThread.SleepTask.Delayです。

どちらも「待機」を実現できますが、内部の動きは大きく異なります。Thread.Sleepは現在のスレッドをブロックして止める方法で、Task.Delayは指定時間後に完了するTaskを返し、awaitと組み合わせて非同期的に待機する方法です。Microsoftの公式ドキュメントでも、Thread.Sleepは現在のスレッドを指定時間中断するメソッド、Task.Delayは指定時間後に完了するタスクを作成するメソッドとして説明されています。Microsoft Learn+1

結論からいうと、現在のC#開発では、基本的にはTask.Delayを使う場面のほうが多いです。特にWPF、WinForms、MAUIなどのUIアプリや、ASP.NET CoreなどのWebアプリでは、Thread.Sleepでスレッドを止めると画面が固まったり、サーバーの処理性能を落としたりする原因になります。

一方で、Thread.Sleepがまったく不要というわけではありません。単純なコンソールアプリ、検証コード、特定のスレッド制御などでは、意図を理解したうえで使うことがあります。

この記事では、C#のSleepDelayの違い、Thread.SleepTask.Delayの使い分け、サンプルコード、よくある間違いまで、実務で迷わないように解説します。

1. C#のSleepとDelayの違いとは?

C#で待機処理を書くときにまず理解したいのは、「処理を止める」のか「非同期で待つ」のかという違いです。

Thread.SleepTask.Delayは、どちらも指定時間の待機を表現できます。しかし、プログラム全体への影響は大きく異なります。

1-1. Thread.SleepとTask.Delayの結論:同期的に止めるか、非同期で待つか

Thread.Sleepは、現在実行中のスレッドを指定時間だけブロックします。つまり、そのスレッドは待機中にほかの処理を進められません。Microsoftの説明でも、Thread.Sleepを呼び出すと現在のスレッドが指定時間ブロックされ、時間が経過すると実行を再開するとされています。Microsoft Learn

一方、Task.Delayは指定時間後に完了するTaskを返します。await Task.Delay(...)と書くことで、メソッドの実行を一時的に中断し、待機中は呼び出し元へ制御を返せます。C#の非同期プログラミングでは、awaitによって呼び出し元へ制御を戻せるため、UIの応答性やサーバーの伸縮性を保ちやすくなります。Microsoft Learn

簡単にいうと、次のような違いです。

比較項目Thread.SleepTask.Delay
待機方法同期的に止める非同期で待つ
スレッドブロックする原則ブロックしない
戻り値voidTask
主な使い方Thread.Sleep(1000);await Task.Delay(1000);
UIアプリ画面が固まりやすい画面を固めにくい
Webアプリスレッドを占有しやすいリクエスト処理で使いやすい
推奨度限定的に使う基本はこちらを使う

1-2. この記事でわかること:違い・使い分け・サンプルコード

この記事では、次の内容を順番に解説します。

内容ポイント
Thread.Sleepの基本スレッドをブロックして待つ方法
Task.Delayの基本非同期で待つ方法
違いの比較同期/非同期、戻り値、UI、Webアプリへの影響
使い分けどちらを選ぶべきかの判断基準
サンプルコード3秒待機、ループ、ボタンクリック、リトライ処理
よくある間違いawait忘れ、.Wait()、デッドロック、タイマー代わりの使用

C#で「sleep」「delay」「待機処理」を調べている方は、単に書き方だけでなく、どの場面でどちらを選ぶべきかまで理解しておくことが重要です。

1-3. 先に使い分け早見表で確認する

まずは、使い分けの結論を早見表で確認しましょう。

やりたいこと推奨理由
コンソールアプリで単純に数秒止めたいThread.Sleepでも可単純な同期処理なら影響が小さい
asyncメソッド内で待機したいTask.Delay非同期処理と相性がよい
UIアプリで画面を固めずに待ちたいTask.DelayUIスレッドをブロックしない
ASP.NET Coreで待機したいTask.Delayスレッドを無駄に占有しにくい
リトライ処理で間隔を空けたいTask.Delay非同期リトライに向いている
テストやデバッグで一時的に止めたい場合による本番コードでは慎重に使う
正確な周期処理をしたいTimerなどSleepDelayは厳密なタイマーではない

迷った場合は、まずTask.Delayを検討するのがおすすめです。特にasyncawaitを使っているコードでは、Thread.Sleepではなくawait Task.Delay(...)を使うのが基本です。

2. Thread.Sleepとは?スレッドをブロックして待機する方法

Thread.Sleepは、System.Threading.Threadクラスに用意されているメソッドです。現在実行中のスレッドを指定時間だけ一時停止します。

「今この処理を完全に止めたい」という同期的な待機には使えますが、待機中のスレッドはほかの作業をできないため、使い方には注意が必要です。

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

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

C#
Thread.Sleep(ミリ秒);

たとえば、1秒待機したい場合は次のように書きます。

C#
Thread.Sleep(1000);

1000は1000ミリ秒、つまり1秒です。

TimeSpanを使って、より読みやすく書くこともできます。

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

Thread.Sleepには、ミリ秒を指定するオーバーロードと、TimeSpanを指定するオーバーロードがあります。公式ドキュメントでも、Sleep(Int32)は指定したミリ秒、Sleep(TimeSpan)は指定した時間だけ現在のスレッドを中断するものとして説明されています。Microsoft Learn

2-2. Thread.Sleepで指定時間だけ処理を止めるサンプルコード

次は、Thread.Sleepで3秒待機するサンプルです。

C#
using System;
using System.Threading;

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

Thread.Sleep(3000);

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

実行すると、処理開始と表示されたあと、3秒間停止し、その後に3秒後に処理再開と表示されます。

このコードでは、Mainメソッドを実行しているメインスレッドが3秒間ブロックされます。コンソールアプリで単純に待機させるだけなら問題になりにくいですが、UIアプリやWebアプリでは注意が必要です。

2-3. Thread.Sleepがスレッドを占有する仕組み

Thread.Sleepを呼び出すと、現在のスレッドは指定時間のあいだ実行対象から外されます。待機時間が過ぎると、再びスケジューリングされ、続きの処理を実行します。

重要なのは、「待機中のスレッドが使えなくなる」という点です。スレッドそのものは存在し続けますが、そのスレッド上では処理を進められません。

たとえば、次のようなコードを考えます。

C#
Console.WriteLine("A");
Thread.Sleep(5000);
Console.WriteLine("B");

この場合、Aを表示したあと、同じスレッドは5秒間停止します。その間、Bは表示されません。

Thread.Sleepは「非同期で待つ」のではなく、「今のスレッドを止める」処理です。

2-4. Thread.Sleepを使うとUIや処理が固まる理由

WPFやWinFormsなどのUIアプリでは、画面描画やボタンクリックなどの操作は通常UIスレッドで処理されます。

そのUIスレッド上でThread.Sleepを呼び出すと、待機中はUIスレッドが止まります。その結果、次のような現象が起きます。

現象理由
画面が固まるUIスレッドがブロックされる
ボタンを押しても反応しないイベント処理が進まない
画面の再描画が遅れる描画処理が実行されない
「応答なし」になるメッセージ処理が滞る

たとえば、ボタンクリックイベントで次のように書くと、3秒間画面が固まる可能性があります。

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

このコードは一見シンプルですが、Thread.Sleep(3000)の間、UIスレッドがブロックされます。そのため、画面の更新や次のユーザー操作を処理できません。

UIアプリでは、基本的にThread.Sleepではなくawait Task.Delay(...)を使うべきです。

2-5. Thread.Sleepを使ってよいケース・避けるべきケース

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

使ってよいケース
簡単なコンソールアプリサンプルコードで数秒待つ
デバッグ目的一時的に処理を止めて状態を確認する
検証用コード動作確認のために待機する
専用スレッド上の待機UIやリクエスト処理に影響しないスレッド

一方、次のようなケースでは避けるべきです。

避けるべきケース理由
UIスレッド上画面が固まる
ASP.NET Coreのリクエスト処理中スレッドを占有する
asyncメソッド内非同期のメリットを失う
リトライ処理Task.Delayのほうが自然
正確な周期処理Timerなどを使うべき

特に、asyncメソッドの中でThread.Sleepを使うのは避けましょう。非同期メソッドであっても、Thread.Sleepを呼べば、その時点でスレッドはブロックされます。

3. Task.Delayとは?非同期処理で待機する方法

Task.Delayは、System.Threading.Tasks.Taskクラスに用意されているメソッドです。指定時間後に完了するTaskを作成します。

awaitと組み合わせることで、スレッドをブロックせずに待機できます。

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

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

C#
await Task.Delay(ミリ秒);

1秒待つ場合は、次のように書きます。

C#
await Task.Delay(1000);

TimeSpanを使うと、待機時間の意味がわかりやすくなります。

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

Task.Delayは、指定時間後に完了するTaskを返します。公式ドキュメントでも、Delay(Int32)は指定ミリ秒後に完了するタスク、Delay(TimeSpan)は指定した時間間隔後に完了するタスクを作成すると説明されています。Microsoft Learn

3-2. async/awaitとTask.Delayを使ったサンプルコード

Task.Delayは、通常asyncawaitとセットで使います。

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

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

await Task.Delay(3000);

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

このコードでは、await Task.Delay(3000)によって、3秒後に処理が再開されます。

Thread.Sleepとの大きな違いは、待機中にスレッドを占有し続けないことです。awaitで待機している間、呼び出し元に制御を返すことができるため、UIアプリでは画面の応答性を保ちやすく、Webアプリではリクエスト処理用のスレッドを有効活用しやすくなります。

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

Task.Delayは「指定時間だけスレッドを眠らせる」のではありません。指定時間後に完了するTaskを作り、その完了をawaitで待ちます。

たとえば、次のコードを見てください。

C#
await Task.Delay(3000);

この行に到達すると、メソッドの実行はいったん中断されます。ただし、Thread.Sleepのようにスレッドをその場で止め続けるわけではありません。awaitは呼び出し元へ制御を返し、Task.Delayが完了したあとに続きの処理を再開します。

C#の非同期プログラミングでは、TaskTask<T>が非同期操作を表す中心的な型であり、asyncキーワードによってメソッド内でawaitを使用できるようになります。awaitを適用すると、タスクが完了するまで呼び出し元へ制御を返します。Microsoft Learn

3-4. UIアプリやWebアプリでTask.Delayが推奨される理由

UIアプリでTask.Delayが推奨される理由は、UIスレッドをブロックしないためです。

たとえば、WPFやWinFormsのボタンクリック処理で次のように書くと、待機中も画面が固まりにくくなります。

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

await Task.Delay(3000);

label1.Text = "完了";
}

このコードでは、3秒待ってからlabel1.Textを更新しますが、Thread.SleepのようにUIスレッドをブロックし続けるわけではありません。

ASP.NET CoreなどのWebアプリでも、Task.DelayThread.Sleepより適しています。ASP.NET Coreのベストプラクティスでは、ブロッキング呼び出しを避けることが推奨されており、非同期APIによって少ないスレッドで多くの同時リクエストを処理しやすくなると説明されています。Microsoft Learn

もちろん、実際のWebアプリで意味なく待機することは多くありません。しかし、リトライ、ポーリング、レート制限、バックオフ処理などでは、await Task.Delay(...)を使う場面があります。

3-5. CancellationTokenでTask.Delayをキャンセルする方法

Task.Delayは、CancellationTokenを使ってキャンセルできます。これはThread.Sleepにはない大きな利点です。

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);

// 2秒後にキャンセル
await Task.Delay(2000);
cts.Cancel();

await task;
}

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

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

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

このコードでは、10秒待機する予定のTask.Delayを2秒後にキャンセルしています。

Task.Delayには、CancellationTokenを受け取るオーバーロードがあります。公式ドキュメントでも、Delay(Int32, CancellationToken)Delay(TimeSpan, CancellationToken)は、指定時間後に完了する取り消し可能なタスクを作成すると説明されています。Microsoft Learn

長い待機、リトライ処理、ユーザー操作によるキャンセル、アプリ終了時の停止などを考える場合は、CancellationToken付きのTask.Delayが便利です。

4. Thread.SleepとTask.Delayの違いを比較

ここからは、Thread.SleepTask.Delayの違いをより具体的に比較します。

単に「どちらも待てる」と考えるのではなく、同期/非同期、スレッド、戻り値、UI、サーバー処理への影響を理解することが大切です。

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

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

C#
Thread.Sleep(3000);

このコードに到達すると、現在のスレッドは3秒間停止します。その間、次の行には進みません。

一方、Task.Delayは非同期的な待機です。

C#
await Task.Delay(3000);

このコードに到達すると、現在のメソッドはいったん中断されますが、スレッドをブロックし続けるわけではありません。待機が完了したら、続きの処理が再開されます。

違いを簡単に表すと次のようになります。

種類コード動き
同期待機Thread.Sleep(3000);今のスレッドを3秒止める
非同期待機await Task.Delay(3000);3秒後に再開するタスクを待つ

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

最も重要な違いは、スレッドをブロックするかどうかです。

Thread.Sleepはブロックします。

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

この間、実行中のスレッドは待機に使われます。

Task.Delayは、awaitと組み合わせることでブロックを避けられます。

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

待機中にスレッドを占有しにくいため、UIアプリやサーバーアプリで有利です。

ただし、次のように書くと意味が変わります。

C#
Task.Delay(3000).Wait();

これはTask.Delayを使っていても、.Wait()で同期的に待っているため、結局スレッドをブロックします。Task.Delayawaitしてこそ、非同期のメリットを活かせます。

4-3. 戻り値の違い:voidとTask

Thread.Sleepの戻り値はvoidです。

C#
Thread.Sleep(1000);

単に現在のスレッドを止めるだけで、非同期操作を表すオブジェクトは返しません。

一方、Task.Delayの戻り値はTaskです。

C#
Task delayTask = Task.Delay(1000);

そのため、次のようにawaitできます。

C#
await delayTask;

Taskを返すということは、非同期処理の流れに組み込めるということです。

たとえば、複数のタスクと組み合わせたり、タイムアウト処理に使ったりできます。MicrosoftのTAPに関するドキュメントでも、Task.Delayはポーリングループ、ユーザー入力処理の遅延、Task.WhenAnyと組み合わせたタイムアウト実装などに使えると説明されています。Microsoft Learn

4-4. CPU・メモリ・スレッドプールへの影響

Thread.SleepはCPUを使い続けて計算する処理ではないため、待機中にCPU使用率が高くなるわけではありません。

しかし、スレッドを占有する点が問題になります。特にスレッドプールのスレッド上でThread.Sleepを多用すると、利用可能なスレッドが減り、ほかの処理が待たされる可能性があります。

たとえば、サーバーアプリで多くのリクエストが同時に来て、それぞれがThread.Sleep(5000)を実行した場合、5秒間スレッドがブロックされます。リクエスト数が増えるほど、スレッド不足や応答遅延につながりやすくなります。

Task.Delayは、待機そのもののためにスレッドを占有し続けません。そのため、多数の待機処理がある場合でも、Thread.Sleepよりスケールしやすいです。

4-5. UIアプリでの挙動の違い

UIアプリでは、違いがとてもわかりやすく現れます。

Thread.Sleepを使った場合は、画面が固まります。

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

Thread.Sleep(3000);

statusLabel.Text = "完了";
}

このコードでは、3秒間UIスレッドが止まるため、画面更新やユーザー操作が遅れます。

Task.Delayを使うと、画面を固めずに待機できます。

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

await Task.Delay(3000);

statusLabel.Text = "完了";
}

UIアプリで一定時間待ちたい場合は、基本的にTask.Delayを選びます。

4-6. ASP.NETなどサーバーサイドでの影響

ASP.NET Coreなどのサーバーサイドでは、Thread.Sleepの使用は特に注意が必要です。

Webアプリでは、多数のリクエストを効率よく処理する必要があります。そこでリクエスト処理中にThread.Sleepを使うと、待機しているだけのためにスレッドを占有してしまいます。

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

C#
[HttpGet]
public IActionResult Get()
{
Thread.Sleep(3000);

return Ok("完了");
}

このコードは3秒間スレッドをブロックします。

非同期アクションで書くなら、次のようにします。

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

return Ok("完了");
}

実際のAPIで意味なく3秒待つことは通常ありませんが、外部サービスのリトライ待機、ポーリング、バックオフなどでは、await Task.Delay(...)を使うほうが適しています。

5. C#でSleepとDelayを使い分ける判断基準

C#でSleepDelayを使い分けるときは、「現在のスレッドを止めてもよいか」を基準に考えます。

スレッドを止めても問題ない単純な場面ならThread.Sleepも使えます。しかし、UI、Web、非同期処理では、基本的にTask.Delayを選びます。

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

現在のC#では、待機処理の基本はTask.Delayです。

理由は次のとおりです。

理由内容
スレッドをブロックしにくい待機中にスレッドを占有しない
async/awaitと相性がよい非同期処理の流れに自然に組み込める
UIアプリに向いている画面を固めずに待機しやすい
Webアプリに向いているサーバーリソースを効率よく使いやすい
キャンセルできるCancellationTokenを使える
リトライ処理に向いているバックオフ待機を実装しやすい

asyncメソッドの中で待機したい場合は、ほぼ必ずawait Task.Delay(...)を使うと考えてよいでしょう。

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

コンソールアプリで単純に数秒止めたいだけなら、Thread.Sleepでも問題になりにくいです。

C#
using System;
using System.Threading;

class Program
{
static void Main()
{
Console.WriteLine("3秒待ちます");
Thread.Sleep(3000);
Console.WriteLine("完了");
}
}

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

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

class Program
{
static async Task Main()
{
Console.WriteLine("3秒待ちます");
await Task.Delay(3000);
Console.WriteLine("完了");
}
}

シンプルなサンプルや検証ならThread.Sleep、非同期処理の流れに乗せるならTask.Delayと考えるとよいです。

5-3. UIアプリで画面を固めずに待機したい場合

UIアプリでは、Task.Delayを使いましょう。

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

C#
private void Button_Click(object sender, EventArgs e)
{
button1.Enabled = false;

Thread.Sleep(3000);

button1.Enabled = true;
}

このコードでは、ボタンを無効化したあと3秒待って再度有効化したい意図があります。しかし、Thread.SleepによってUIスレッドが止まるため、画面が固まる可能性があります。

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

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

await Task.Delay(3000);

button1.Enabled = true;
}

待機中もUIスレッドが完全にブロックされないため、アプリの応答性を保ちやすくなります。

5-4. Webアプリ・APIでリクエスト処理中に待機したい場合

WebアプリやAPIでは、Thread.Sleepを避け、Task.Delayを使います。

悪い例です。

C#
public IActionResult Get()
{
Thread.Sleep(1000);

return Ok();
}

よい例です。

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

return Ok();
}

ただし、Webアプリでは「何のために待機しているのか」も重要です。単に処理を遅らせるだけなら、そもそも待機処理が不要かもしれません。

Task.Delayを使うのは、たとえば次のような場面です。

場面
リトライ外部API失敗後に少し待って再試行する
バックオフ失敗回数に応じて待機時間を増やす
ポーリング一定時間ごとに状態確認する
レート制限対応短時間に呼び出しすぎないようにする

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

テストコードやデバッグでは、Thread.Sleepが使われることがあります。

C#
Thread.Sleep(1000);

しかし、自動テストでThread.Sleepを多用すると、テストが遅く、不安定になります。

たとえば、「1秒待てば処理が終わっているはず」という書き方は、実行環境によって失敗する可能性があります。

C#
// あまりよくない例
StartBackgroundProcess();
Thread.Sleep(1000);
Assert.True(IsCompleted);

よりよい方法は、完了条件を待つことです。

C#
StartBackgroundProcess();

var timeout = TimeSpan.FromSeconds(5);
var start = DateTime.UtcNow;

while (!IsCompleted)
{
if (DateTime.UtcNow - start > timeout)
{
throw new TimeoutException("処理が完了しませんでした。");
}

await Task.Delay(100);
}

Assert.True(IsCompleted);

さらに本格的には、イベント、TaskCompletionSource、モック、仮想時間、専用のテストユーティリティを使うとよいです。

5-6. ループ処理やリトライ処理で待機したい場合

ループ処理やリトライ処理では、Task.Delayが向いています。

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

await Task.Delay(1000);
}

リトライ処理では、失敗したあとに少し待って再試行できます。

C#
for (int attempt = 1; attempt <= 3; attempt++)
{
try
{
await CallApiAsync();
break;
}
catch (Exception) when (attempt < 3)
{
await Task.Delay(1000);
}
}

非同期メソッドの中でループやリトライを行うなら、Thread.Sleepではなくawait Task.Delay(...)を使いましょう。

6. サンプルコードで見るSleepとDelayの使い方

ここからは、Thread.SleepTask.Delayの使い方をサンプルコードで確認します。

単純な3秒待機、ループ処理、UIアプリ、リトライ処理、.Wait()の注意点まで見ていきましょう。

6-1. Thread.Sleepで3秒待機するサンプル

C#
using System;
using System.Threading;

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

Thread.Sleep(3000);

Console.WriteLine("3秒経過");
}
}

このコードでは、Thread.Sleep(3000)によって、現在のスレッドを3秒間停止します。

単純なコンソールアプリや検証コードではわかりやすい書き方です。

ただし、UIアプリやWebアプリの本番コードでは、安易に使わないようにしましょう。

6-2. Task.Delayで3秒待機するサンプル

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

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

await Task.Delay(3000);

Console.WriteLine("3秒経過");
}
}

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

Task.DelayTaskを返すため、awaitと組み合わせて使います。

async Task Main()は、C# 7.1以降で利用できる非同期のエントリーポイントです。古い環境では、Mainから別の非同期メソッドを呼び出す形にすることもあります。

6-3. forループ内で一定間隔ごとに処理するサンプル

1秒ごとに処理を実行するサンプルです。

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

class Program
{
static async Task Main()
{
for (int i = 1; i <= 5; i++)
{
Console.WriteLine($"{i}回目の処理");

await Task.Delay(1000);
}

Console.WriteLine("完了");
}
}

このコードは、1回処理するたびに1秒待機します。

ただし、厳密に「1秒ぴったりごと」に実行されるわけではありません。処理自体にかかる時間やOSのスケジューリングなどの影響を受けます。

一定間隔で長期間実行する処理や、より正確な周期処理には、PeriodicTimerTimerなどの利用を検討しましょう。

6-4. ボタンクリック後にTask.Delayで待機するサンプル

WinFormsやWPFなどのUIアプリでは、ボタンクリック後に一定時間待つ処理を書くことがあります。

WinFormsの例です。

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

await Task.Delay(3000);

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

このコードでは、ボタンを押したあと3秒待機してからラベルを更新します。

Thread.SleepではなくTask.Delayを使っているため、待機中もUIスレッドをブロックしにくく、画面が固まりにくくなります。

WPFの場合も考え方は同じです。

C#
private async void Button_Click(object sender, RoutedEventArgs e)
{
MyButton.IsEnabled = false;
StatusText.Text = "3秒待機中...";

await Task.Delay(3000);

StatusText.Text = "完了";
MyButton.IsEnabled = true;
}

UIイベントハンドラではasync voidが使われることがありますが、通常のメソッドではasync Taskを返すのが基本です。

6-5. リトライ処理でTask.Delayを使うサンプル

外部APIやデータベース接続などでは、一時的な失敗に対してリトライすることがあります。

次は、失敗したら1秒、2秒、4秒と待機時間を増やしながらリトライするサンプルです。

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

class Program
{
static async Task Main()
{
try
{
string result = await RetryAsync(CallApiAsync, maxRetryCount: 3);
Console.WriteLine(result);
}
catch (Exception ex)
{
Console.WriteLine($"最終的に失敗しました: {ex.Message}");
}
}

static async Task<string> RetryAsync(
Func<Task<string>> operation,
int maxRetryCount)
{
for (int attempt = 1; attempt <= maxRetryCount; attempt++)
{
try
{
return await operation();
}
catch when (attempt < maxRetryCount)
{
int delayMilliseconds = (int)Math.Pow(2, attempt - 1) * 1000;

Console.WriteLine($"{attempt}回目に失敗。{delayMilliseconds}ミリ秒後に再試行します。");

await Task.Delay(delayMilliseconds);
}
}

return await operation();
}

static async Task<string> CallApiAsync()
{
await Task.Delay(500);

throw new Exception("API呼び出しに失敗しました。");
}
}

リトライ処理では、Thread.SleepではなくTask.Delayを使うことで、待機中にスレッドをブロックしにくくなります。

実務では、リトライ回数、待機時間、キャンセル、ログ出力、対象例外の絞り込みなども考慮します。

6-6. Task.Delay().Wait()を使う場合の注意点

Task.Delayは非同期的な待機に使えますが、次のように.Wait()を付けると同期的な待機になります。

C#
Task.Delay(3000).Wait();

これは実質的に、現在のスレッドを3秒間ブロックする処理です。

つまり、Task.Delayを使っていても、.Wait()を呼び出すと非同期のメリットは失われます。

特にUIアプリやASP.NETでは、次のような書き方は避けましょう。

C#
// 避けたい例
Task.Delay(3000).Wait();

基本は次のように書きます。

C#
await Task.Delay(3000);

どうしても同期メソッドから非同期処理を呼ぶ必要がある場合は、設計を見直し、呼び出し元までasyncにできないか検討しましょう。

7. Thread.SleepとTask.Delayでよくある間違い

Thread.SleepTask.Delayはシンプルに見えますが、実務ではよくある落とし穴があります。

ここでは、特に注意したい間違いを解説します。

7-1. asyncメソッド内でThread.Sleepを使ってしまう

よくある間違いが、asyncメソッド内でThread.Sleepを使うことです。

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

Thread.Sleep(3000);

Console.WriteLine("終了");
}

このメソッドはasyncと書かれていますが、Thread.Sleepの部分ではスレッドをブロックしています。

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

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

await Task.Delay(3000);

Console.WriteLine("終了");
}

asyncメソッド内の待機処理は、基本的にawait Task.Delay(...)を使いましょう。

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

Task.Delayで非常によくあるミスが、awaitを付け忘れることです。

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

Task.Delay(3000);

Console.WriteLine("終了");
}

このコードでは、Task.Delay(3000)を呼び出していますが、awaitしていません。そのため、3秒待たずに次の行へ進みます。

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

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

await Task.Delay(3000);

Console.WriteLine("終了");
}

Task.Delayは、呼び出しただけでは「その場で待つ」わけではありません。待機したいならawaitが必要です。

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

非同期処理を同期的に待つために、.Wait().Resultを使うケースがあります。

C#
Task.Delay(3000).Wait();

または次のようなコードです。

C#
var result = SomeAsyncMethod().Result;

これらは、UIアプリや古いASP.NET環境などでデッドロックや応答停止の原因になることがあります。

Task.Delay().Wait()は単純な待機なので常にデッドロックするとは限りませんが、非同期処理を同期的に待つ癖は避けたほうが安全です。

基本は次のように、呼び出し元までasyncにします。

C#
await Task.Delay(3000);

非同期処理では、「async all the way」、つまり可能な限り呼び出し元まで非同期でつなげる設計が重要です。

7-4. SleepやDelayを正確なタイマーとして使ってしまう

Thread.SleepTask.Delayは、指定したミリ秒どおりに必ず再開する厳密なタイマーではありません。

たとえば、次のコードは「1秒ごと」に見えます。

C#
while (true)
{
DoWork();

await Task.Delay(1000);
}

しかし実際には、DoWork()にかかる時間も加わります。DoWork()が200ミリ秒かかれば、次の処理開始までおおよそ1.2秒かかります。

また、OSのスケジューリングやシステムクロックの解像度の影響も受けます。Microsoftのドキュメントでも、Thread.Sleepの実際のタイムアウトはシステムクロックの解像度などの影響で指定値と完全には一致しない可能性があること、Task.Delayもシステムクロックに依存することが説明されています。Microsoft Learn+1

正確な周期実行が必要な場合は、次のような仕組みを検討しましょう。

目的候補
一定間隔で処理PeriodicTimer
UIで定期更新WPFのDispatcherTimer、WinFormsのTimer
バックグラウンド処理System.Threading.Timer
高精度な計測Stopwatch
スケジュール実行ジョブスケジューラ、Quartz.NETなど

7-5. 待機時間をマジックナンバーで書いてしまう

次のように待機時間を直接書くと、意味がわかりにくくなります。

C#
await Task.Delay(3000);

短いコードなら問題ありませんが、実務では定数化すると読みやすくなります。

C#
private static readonly TimeSpan RetryDelay = TimeSpan.FromSeconds(3);

await Task.Delay(RetryDelay);

または、用途に応じた名前を付けます。

C#
private static readonly TimeSpan ApiRetryInterval = TimeSpan.FromSeconds(3);
private static readonly TimeSpan UiMessageDisplayTime = TimeSpan.FromSeconds(2);
private static readonly TimeSpan PollingInterval = TimeSpan.FromSeconds(5);

待機時間に名前を付けることで、「なぜその時間待つのか」が伝わりやすくなります。

8. SleepとDelayに関するよくある質問

最後に、C#のSleepDelayに関するよくある質問をまとめます。

8-1. Thread.Sleepは非推奨なのか?

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

ただし、UIアプリ、Webアプリ、非同期メソッド内では避けるべき場面が多いです。

Thread.Sleepは、「現在のスレッドを止めたい」という明確な意図がある場合に使います。単に一定時間待ちたいだけなら、ほとんどの場合はTask.Delayのほうが適しています。

特に次の場面ではTask.Delayを選びましょう。

場面推奨
asyncメソッド内await Task.Delay(...)
UIアプリawait Task.Delay(...)
Webアプリawait Task.Delay(...)
リトライ処理await Task.Delay(...)

8-2. Task.Delayは完全に指定ミリ秒どおり待機するのか?

完全に指定ミリ秒どおりとは限りません。

Task.Delay(1000)は、基本的には約1秒後に完了するタスクを作ります。しかし、OSのスケジューリング、システムクロック、実行環境の負荷などによって、実際の再開タイミングは多少ずれることがあります。

そのため、Task.Delayは「おおよその待機」には向いていますが、高精度なタイマーやリアルタイム制御には向いていません。

処理時間を計測したい場合はStopwatch、一定周期で処理したい場合はTimerPeriodicTimerなどを検討しましょう。

8-3. Thread.Sleep(0)にはどんな意味があるのか?

Thread.Sleep(0)は、通常の待機とは少し意味が異なります。

Thread.Sleep(0)を呼び出すと、現在のスレッドは残りのタイムスライスを、実行可能な同じ優先度のスレッドに譲ります。実行可能な同じ優先度のスレッドがなければ、現在のスレッドは停止されません。これはMicrosoftのThread.Sleepドキュメントにも説明されています。Microsoft Learn

通常のアプリケーションコードでThread.Sleep(0)を使う場面は多くありません。

スレッドスケジューリングに関する低レベルな制御を理解している場合を除き、一般的な待機処理には使わないほうがよいでしょう。

8-4. Thread.Sleep(Timeout.Infinite)は何に使うのか?

Thread.Sleep(Timeout.Infinite)は、無期限にスレッドを停止させる指定です。

C#
Thread.Sleep(Timeout.Infinite);

公式ドキュメントでは、Timeout.Infiniteを指定するとスレッドを無期限に中断できるものの、スレッド同期やリソース管理にはMutexMonitorEventWaitHandleSemaphoreなどの使用が推奨されています。Microsoft Learn

つまり、無期限に待ちたいからといって、安易にThread.Sleep(Timeout.Infinite)を使うべきではありません。

何かのイベントを待ちたい場合は、次のような専用の仕組みを使うほうが適切です。

目的候補
キャンセル可能に待つCancellationToken
スレッド間通知ManualResetEventSlimAutoResetEvent
排他制御lockMonitorSemaphoreSlim
非同期の完了待ちTaskCompletionSource

8-5. Task.DelayとTimerはどう使い分けるのか?

Task.DelayTimerは、どちらも時間に関係する処理で使われますが、用途が異なります。

用途推奨
一度だけ数秒待つTask.Delay
リトライ前に待つTask.Delay
asyncメソッド内で少し待つTask.Delay
一定間隔で繰り返し実行TimerPeriodicTimer
UIを定期更新UIフレームワークのTimer
長期的なスケジュール処理ジョブスケジューラ

Task.Delayは「この場所で少し待ってから次に進む」処理に向いています。

一方、Timerは「一定間隔でコールバックを実行する」処理に向いています。

.NET 6以降で非同期ループをきれいに書きたい場合は、PeriodicTimerも選択肢になります。

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

while (await timer.WaitForNextTickAsync())
{
Console.WriteLine("1秒ごとの処理");
}

単発の待機ならTask.Delay、定期実行ならTimer系と考えるとわかりやすいです。

8-6. UnityやWPF、WinFormsではどちらを使うべきか?

WPFやWinFormsでは、基本的にTask.Delayを使います。

C#
private async void Button_Click(object sender, EventArgs e)
{
await Task.Delay(1000);
}

Thread.SleepをUIスレッドで使うと、画面が固まりやすくなります。

Unityの場合は少し事情が異なります。Unityでは、一定時間待つ処理にコルーチンとWaitForSecondsがよく使われます。

C#
IEnumerator WaitAndRun()
{
yield return new WaitForSeconds(1f);

Debug.Log("1秒後に実行");
}

UnityでもC#のTask.Delayを使える場面はありますが、Unityのメインスレッドやゲームループとの関係を考える必要があります。通常のゲーム内待機や演出では、Unityのコルーチンを使うほうが自然なケースが多いです。

WPF、WinForms、MAUIなどの一般的な.NET UIアプリでは、Thread.Sleepではなくawait Task.Delay(...)を使いましょう。

まとめ

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

Thread.Sleepは、現在のスレッドを指定時間だけブロックする同期的な待機です。シンプルなコンソールアプリや検証コードでは使えますが、UIアプリやWebアプリでは画面停止やスレッド占有の原因になるため注意が必要です。

一方、Task.Delayは、指定時間後に完了するTaskを返す非同期的な待機です。awaitと組み合わせることで、スレッドをブロックしにくく、UIアプリやWebアプリ、リトライ処理、ポーリング処理などに向いています。

使い分けの基本は次のとおりです。

状況選ぶべき方法
asyncメソッド内で待つawait Task.Delay(...)
UIを固めずに待つawait Task.Delay(...)
Web APIで待機するawait Task.Delay(...)
リトライ間隔を空けるawait Task.Delay(...)
コンソールアプリで単純に止めるThread.Sleepでも可
デバッグで一時停止するThread.Sleepでも可
正確な周期処理をするTimerPeriodicTimer

迷った場合は、まずTask.Delayを選ぶと考えて問題ありません。

特に、C#でasyncawaitを使っているコードでは、Thread.Sleepではなくawait Task.Delay(...)を使うのが基本です。Thread.Sleepは「現在のスレッドを止める必要がある」と明確に判断できる場合にだけ、限定的に使うようにしましょう。