C#のSleep完全解説|Thread.SleepとTask.Delayの違い・待機処理の正しい使い方
はじめに
C#で「一定時間だけ処理を止めたい」「1秒待ってから次の処理を実行したい」と思ったとき、多くの人がまず検索するのが「csharp sleep」や「C# Sleep」です。
C#で待機処理を行う代表的な方法には、Thread.Sleep と Task.Delay があります。どちらも「待つ」ために使えますが、仕組みや適した場面は大きく異なります。
特に重要なのは、Thread.Sleep は現在のスレッドを止めるのに対し、Task.Delay は非同期的に待機できるという点です。この違いを理解せずに使うと、UIアプリが固まったり、Webアプリのパフォーマンスが低下したり、非同期処理が正しく動かない原因になります。
この記事では、C#のSleepにあたる待機処理について、Thread.Sleep と Task.Delay の基本から違い、使い分け、実践的なサンプル、よくある疑問、ベストプラクティスまで詳しく解説します。
1. C#のSleepとは?待機処理でできること
1-1. Sleepは処理を一時停止させるための仕組み
C#におけるSleepとは、プログラムの処理を一定時間止めるための仕組みを指します。
たとえば、次のような処理を考えてみます。
C#Console.WriteLine("開始");
Thread.Sleep(1000);
Console.WriteLine("1秒後に表示");
このコードでは、「開始」と表示されたあと、1秒待ってから「1秒後に表示」と出力されます。
このように、Sleepは処理の流れを一時的に止めたいときに使われます。ただし、C#では単純に「待つ」といっても、現在のスレッドを完全に止める方法と、非同期的に待機する方法があります。
その代表が Thread.Sleep と Task.Delay です。
1-2. C#で待機処理が必要になる主な場面
C#で待機処理が必要になる場面は多くあります。
たとえば、以下のようなケースです。
一定時間後に処理を実行したい場合、外部APIへの再試行間隔を空けたい場合、ループ処理を一定間隔で実行したい場合、テストやデバッグで一時的に処理を遅らせたい場合、UI上で数秒後にメッセージを表示したい場合などです。
ただし、待機処理は便利な一方で、使い方を間違えるとアプリケーション全体の動作に悪影響を与えることがあります。
特に、UIアプリやWebアプリでは、安易に Thread.Sleep を使うべきではありません。画面が固まったり、サーバーの処理能力を無駄に消費したりする原因になるためです。
1-3. 「sleep関数」はC#に存在するのか
C言語や他の言語に慣れている人は、「C#にもsleep関数があるのでは?」と思うかもしれません。
結論からいうと、C#にはグローバルな sleep 関数は存在しません。
C#で待機処理を行う場合は、主に以下を使います。
C#Thread.Sleep(1000);
または、非同期処理では次のように書きます。
C#await Task.Delay(1000);
つまり、C#で「sleep」と検索している場合、実際には Thread.Sleep または Task.Delay の使い方を理解する必要があります。
1-4. Thread.SleepとTask.Delayがよく使われる理由
Thread.Sleep と Task.Delay がよく使われる理由は、どちらも簡単に待機処理を書けるからです。
Thread.Sleep は同期的なコードで手軽に使えます。
C#Thread.Sleep(1000);
一方、Task.Delay は非同期処理と相性がよく、async/await と組み合わせて使えます。
C#await Task.Delay(1000);
現在のC#開発では、コンソールアプリ、WPF、WinForms、ASP.NET、API通信など、さまざまな場面で非同期処理が使われます。そのため、単に処理を止めたいだけでなく、スレッドを無駄にブロックしない Task.Delay の重要性が高くなっています。
2. Thread.Sleepの基本的な使い方
2-1. Thread.Sleepとは
Thread.Sleep は、現在実行中のスレッドを指定した時間だけ停止するメソッドです。
使用するには、通常以下の名前空間を使います。
C#using System.Threading;
基本形は次のとおりです。
C#Thread.Sleep(待機時間);
Thread.Sleep を呼び出すと、そのスレッドは指定時間が経過するまで処理を再開しません。
重要なのは、「処理だけを待たせる」のではなく、「スレッドそのものを停止させる」という点です。
2-2. ミリ秒を指定して一定時間停止する方法
Thread.Sleep では、待機時間をミリ秒単位で指定できます。
C#Thread.Sleep(500);
このコードは、現在のスレッドを500ミリ秒、つまり0.5秒停止します。
よく使う指定は以下のとおりです。
C#Thread.Sleep(1000); // 1秒
Thread.Sleep(2000); // 2秒
Thread.Sleep(5000); // 5秒
Thread.Sleep(10000); // 10秒
C#のSleepでよくあるミスは、秒とミリ秒を混同することです。
Thread.Sleep(1) は1秒ではなく、1ミリ秒です。1秒待つには Thread.Sleep(1000) と書く必要があります。
2-3. TimeSpanを使って待機時間を指定する方法
Thread.Sleep では、整数のミリ秒だけでなく、TimeSpan を使って待機時間を指定することもできます。
C#Thread.Sleep(TimeSpan.FromSeconds(1));
この書き方のメリットは、待機時間の意味がわかりやすいことです。
C#Thread.Sleep(TimeSpan.FromMilliseconds(500));
Thread.Sleep(TimeSpan.FromSeconds(3));
Thread.Sleep(TimeSpan.FromMinutes(1));
Thread.Sleep(60000) と書くよりも、次のように書いたほうが読みやすくなります。
C#Thread.Sleep(TimeSpan.FromMinutes(1));
本番コードでは、待機時間を数字だけで直接書くより、TimeSpan や定数を使って意味を明確にするのがおすすめです。
2-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秒後に次のメッセージが表示されます。
コンソールアプリの簡単な待機処理であれば、このように Thread.Sleep を使っても問題ない場面があります。
ただし、UIアプリやWebアプリでは注意が必要です。
2-5. Thread.Sleep(0)とThread.Sleep(Timeout.Infinite)の意味
Thread.Sleep には少し特殊な使い方もあります。
まず、Thread.Sleep(0) です。
C#Thread.Sleep(0);
これは、現在のスレッドに割り当てられている残りの実行時間を手放し、同じ優先度の他のスレッドに実行機会を譲るために使われます。
通常の「一定時間待つ」という用途ではあまり使いません。
次に、Thread.Sleep(Timeout.Infinite) です。
C#Thread.Sleep(Timeout.Infinite);
これは、スレッドを無期限に停止させる指定です。別のスレッドから割り込みなどが発生しない限り、処理は戻ってきません。
通常のアプリケーション開発で安易に使うべきではありません。無期限待機が必要な場合でも、キャンセル可能な仕組みや同期用のクラスを使うほうが安全です。
3. Task.Delayの基本的な使い方
3-1. Task.Delayとは
Task.Delay は、指定した時間が経過したあとに完了するタスクを返すメソッドです。
基本形は次のとおりです。
C#await Task.Delay(1000);
Thread.Sleep と似ていますが、決定的に違うのは、Task.Delay は現在のスレッドをブロックしにくいという点です。
Task.Delay は非同期処理で使われることが多く、async/await と組み合わせることで、待機中にスレッドを他の処理へ解放できます。
3-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秒後に処理が再開されます。
このとき、Thread.Sleep のようにスレッドを占有し続けるわけではありません。そのため、UIアプリやWebアプリなど、応答性やスケーラビリティが重要な場面で有効です。
3-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 Task.Delay(1000);
Console.WriteLine("1秒後に処理を再開しました");
}
}
Task.Delay(1000) も Thread.Sleep(1000) と同じく、1000ミリ秒、つまり1秒を表します。
ただし、使い方としては必ず await を付けるのが基本です。
C#await Task.Delay(1000);
await を付け忘れると、待機が完了する前に次の処理へ進んでしまうことがあります。
3-4. 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);
try
{
await task;
}
catch (TaskCanceledException)
{
Console.WriteLine("待機処理がキャンセルされました");
}
}
static async Task WaitAsync(CancellationToken cancellationToken)
{
Console.WriteLine("5秒待機を開始します");
await Task.Delay(5000, cancellationToken);
Console.WriteLine("5秒待機が完了しました");
}
}
この例では、本来5秒待つ処理を、1秒後にキャンセルしています。
リトライ処理、ポーリング処理、アプリ終了時の待機停止などでは、キャンセル可能な Task.Delay が非常に便利です。
3-5. Task.Delayを使うとUIやスレッドをブロックしにくい理由
Task.Delay がUIやスレッドをブロックしにくい理由は、待機中にスレッドを占有し続けないからです。
たとえば、WPFやWinFormsでボタンクリック後に3秒待つ処理を書く場合、Thread.Sleep を使うとUIスレッドが停止します。その結果、画面が固まったように見えます。
一方、await Task.Delay(3000) を使えば、待機中もUIスレッドがメッセージ処理を継続できるため、画面の応答性を保ちやすくなります。
そのため、現代のC#開発では、非同期で待つ必要がある場面では Task.Delay を使うのが基本です。
4. Thread.SleepとTask.Delayの違い
4-1. 最大の違いはスレッドをブロックするかどうか
Thread.Sleep と Task.Delay の最大の違いは、スレッドをブロックするかどうかです。
Thread.Sleep は、現在のスレッドを指定時間だけ停止します。
C#Thread.Sleep(1000);
この間、そのスレッドは他の処理を実行できません。
一方、Task.Delay は、指定時間後に完了するタスクを作成します。
C#await Task.Delay(1000);
await によって処理はいったん中断されますが、スレッドを占有し続けるわけではありません。
つまり、単純に「1秒待つ」という結果は似ていても、内部の動きはまったく異なります。
4-2. 同期処理と非同期処理の違い
Thread.Sleep は同期的な待機です。
C#Console.WriteLine("A");
Thread.Sleep(1000);
Console.WriteLine("B");
このコードでは、スレッドが1秒間止まり、その後に次の行へ進みます。
一方、Task.Delay は非同期的な待機です。
C#Console.WriteLine("A");
await Task.Delay(1000);
Console.WriteLine("B");
この場合、処理の流れとしては1秒後にBが表示されますが、待機中にスレッドを効率よく解放できます。
同期処理では、処理が終わるまで呼び出し元が待ちます。非同期処理では、完了を待ちながらもスレッドを占有しにくくできます。
この違いは、アプリケーションの応答性や同時処理性能に大きく関わります。
4-3. CPU・スレッド・パフォーマンスへの影響
Thread.Sleep は、CPUを激しく消費する処理ではありません。スリープ中のスレッドは実行されないため、CPU使用率は下がります。
しかし、問題はスレッドを占有したまま待つことです。
スレッドは有限のリソースです。特にWebアプリやサーバーアプリでは、多数のリクエストを処理するためにスレッドを効率よく使う必要があります。
Thread.Sleep でスレッドを止めてしまうと、そのスレッドは他のリクエスト処理に使えません。結果として、同時処理性能が低下する可能性があります。
一方、Task.Delay は待機中にスレッドを解放しやすいため、スケーラブルな処理を書きやすくなります。
4-4. UIアプリでThread.Sleepを使うと固まる理由
WPFやWinFormsなどのUIアプリでは、画面の描画やクリック操作の処理をUIスレッドが担当しています。
このUIスレッド上で Thread.Sleep を呼ぶと、UIスレッド自体が停止します。
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の応答性を保ちやすくなります。
4-5. WebアプリやASP.NETでThread.Sleepを避けるべき理由
ASP.NETなどのWebアプリで Thread.Sleep を使うと、リクエスト処理中のスレッドを無駄に停止させてしまいます。
C#public IActionResult Index()
{
Thread.Sleep(3000);
return View();
}
このコードでは、レスポンスを返すまで3秒待ちます。その間、リクエストを処理しているスレッドは使えません。
アクセス数が少ない場合は問題が見えにくいかもしれませんが、同時アクセスが増えるとスレッド不足やレスポンス遅延の原因になります。
非同期で待機する必要がある場合は、次のように書きます。
C#public async Task<IActionResult> Index()
{
await Task.Delay(3000);
return View();
}
ただし、Webアプリで単に待つだけの処理は設計上不要なことも多いため、本当に待機が必要かどうかも検討すべきです。
4-6. Thread.SleepとTask.Delayの使い分け早見表
Thread.Sleep と Task.Delay の使い分けは、次のように考えるとわかりやすいです。
| 場面 | 推奨 |
|---|---|
| コンソールアプリで簡単に待つ | Thread.Sleepでも可 |
| UIアプリで待つ | Task.Delay |
| ASP.NETで待つ | Task.Delay |
| 非同期メソッド内で待つ | Task.Delay |
| テストで一時的に止める | Thread.Sleepも可。ただし注意 |
| キャンセル可能な待機 | Task.Delay |
| リトライ間隔を空ける | Task.Delay |
| スレッドを明示的に停止したい | Thread.Sleep |
基本方針としては、同期的な簡単な処理では Thread.Sleep、非同期処理や実運用のアプリケーションでは Task.Delay を優先するとよいでしょう。
5. C#でSleepを使うべき場面・避けるべき場面
5-1. Thread.Sleepを使ってもよいケース
Thread.Sleep は悪いメソッドではありません。使う場面を選べば有効です。
たとえば、コンソールアプリで簡単に処理を遅らせたい場合、サンプルコードで待機処理を説明する場合、デバッグ中に一時的に処理を止めたい場合、バックグラウンドの専用スレッドを明示的に待たせたい場合などです。
以下のような簡単なコンソールアプリでは、Thread.Sleep を使っても大きな問題にはなりにくいです。
C#Console.WriteLine("3秒待ちます");
Thread.Sleep(3000);
Console.WriteLine("完了");
ただし、UIスレッド、リクエスト処理スレッド、スレッドプール上での安易な使用は避けるべきです。
5-2. Task.Delayを使うべきケース
Task.Delay を使うべきなのは、非同期処理として自然に待機したいケースです。
たとえば、UIアプリで画面を固めずに待つ場合、ASP.NETで非同期処理中に待つ場合、APIリトライの間隔を空ける場合、キャンセル可能な待機処理を作りたい場合、ポーリング処理を非同期で実行したい場合などです。
C#await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
Task.Delay は、CancellationToken と組み合わせられる点も大きなメリットです。
本番コードで待機処理を書くなら、まず Task.Delay を検討するのがおすすめです。
5-3. リトライ処理・ポーリング処理での待機方法
API呼び出しや外部サービスへの接続では、一時的な失敗に備えてリトライ処理を書くことがあります。
このとき、失敗した直後に何度も連続で再試行すると、相手のサーバーに負荷をかけたり、自分のアプリのリソースを無駄に消費したりします。
そのため、リトライ間隔を空ける必要があります。
C#for (int i = 0; i < 3; i++)
{
try
{
await CallApiAsync();
break;
}
catch
{
await Task.Delay(1000);
}
}
このような非同期リトライでは、Thread.Sleep ではなく Task.Delay を使うのが基本です。
ポーリング処理でも同様です。
C#while (!cancellationToken.IsCancellationRequested)
{
await CheckStatusAsync();
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
}
定期的に状態を確認する処理では、キャンセル可能な Task.Delay を使うことで、安全に停止できる設計になります。
5-4. テストコードやデバッグ目的で使う場合の注意点
テストコードで Thread.Sleep を使うことはありますが、安易に使うと不安定なテストの原因になります。
たとえば、次のようなテストは環境によって失敗する可能性があります。
C#Thread.Sleep(1000);
Assert.True(result);
「1秒待てば処理が終わっているはず」という前提は、マシンの負荷や実行環境によって崩れることがあります。
テストでは、固定時間のSleepよりも、条件が満たされるまで待つ仕組みを使うほうが安定します。
C#var timeout = DateTime.UtcNow.AddSeconds(5);
while (!IsCompleted() && DateTime.UtcNow < timeout)
{
await Task.Delay(100);
}
Assert.True(IsCompleted());
デバッグ目的で一時的に Thread.Sleep を使うのは問題ありませんが、そのまま本番コードに残さないよう注意しましょう。
5-5. 本番コードで安易なSleepを避けるべき理由
本番コードで安易にSleepを使うと、根本的な問題を隠してしまうことがあります。
たとえば、「処理が終わるまで少し待つ」という目的でSleepを入れる場合、本来はイベント、コールバック、非同期完了通知、状態確認などで制御すべきかもしれません。
Sleepは時間で解決する方法です。しかし、実際の処理完了時間は環境や負荷によって変わります。
1秒で足りるときもあれば、3秒必要なときもあります。逆に、すぐ終わっているのに無駄に待ってしまうこともあります。
本番コードでは、「なぜ待つ必要があるのか」を明確にし、可能であればイベント駆動や非同期完了を待つ設計にすることが重要です。
6. 実践サンプルで学ぶC#の待機処理
6-1. コンソールアプリで一定時間待つサンプル
まずは、コンソールアプリで一定時間待つ基本例です。
C#using System;
using System.Threading;
class Program
{
static void Main()
{
Console.WriteLine("処理開始");
Thread.Sleep(2000);
Console.WriteLine("2秒後に処理再開");
}
}
同期的なコンソールアプリでは、Thread.Sleep を使うとシンプルに書けます。
ただし、非同期のコンソールアプリにする場合は、次のように Task.Delay を使えます。
C#using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
Console.WriteLine("処理開始");
await Task.Delay(2000);
Console.WriteLine("2秒後に処理再開");
}
}
6-2. forループの中で一定間隔ごとに処理するサンプル
一定間隔で処理を実行したい場合、ループ内で待機処理を入れます。
同期的に書くなら次のようになります。
C#for (int i = 1; i <= 5; i++)
{
Console.WriteLine($"{i}回目の処理");
Thread.Sleep(1000);
}
このコードは、1秒ごとに処理を5回実行します。
非同期で書く場合は、次のようにします。
C#for (int i = 1; i <= 5; i++)
{
Console.WriteLine($"{i}回目の処理");
await Task.Delay(1000);
}
UIアプリやWebアプリでは、後者の Task.Delay を使うほうが適しています。
6-3. 非同期メソッド内でTask.Delayを使うサンプル
非同期メソッド内で待機する場合は、Task.Delay を使うのが基本です。
C#static async Task ProcessAsync()
{
Console.WriteLine("非同期処理を開始");
await Task.Delay(TimeSpan.FromSeconds(2));
Console.WriteLine("非同期処理を再開");
}
呼び出し側は次のように書きます。
C#await ProcessAsync();
非同期メソッドの中で Thread.Sleep を使うと、せっかくの非同期処理なのにスレッドをブロックしてしまいます。
避けたい例は次のとおりです。
C#static async Task BadProcessAsync()
{
Thread.Sleep(2000);
await SomeAsyncMethod();
}
非同期メソッド内では、基本的に次のように書きましょう。
C#await Task.Delay(2000);
6-4. ボタンクリック後に待機するWPF・WinFormsの例
WPFやWinFormsで、ボタンクリック後に数秒待ってから表示を変更したい場合を考えます。
悪い例は次のようなコードです。
C#private void Button_Click(object sender, EventArgs e)
{
Thread.Sleep(3000);
label1.Text = "完了しました";
}
このコードでは、3秒間UIが固まる可能性があります。
正しくは、イベントハンドラーを async にして Task.Delay を使います。
C#private async void Button_Click(object sender, EventArgs e)
{
button1.Enabled = false;
await Task.Delay(3000);
label1.Text = "完了しました";
button1.Enabled = true;
}
この方法なら、待機中も画面の応答性を保ちやすくなります。
WPFでも考え方は同じです。
C#private async void Button_Click(object sender, RoutedEventArgs e)
{
MyButton.IsEnabled = false;
await Task.Delay(3000);
MessageTextBlock.Text = "完了しました";
MyButton.IsEnabled = true;
}
6-5. API呼び出しのリトライ間隔を空ける例
外部API呼び出しで一時的なエラーが発生した場合、少し待ってから再試行することがあります。
C#static async Task<string> CallApiWithRetryAsync(CancellationToken cancellationToken)
{
const int maxRetryCount = 3;
for (int attempt = 1; attempt <= maxRetryCount; attempt++)
{
try
{
return await CallApiAsync(cancellationToken);
}
catch when (attempt < maxRetryCount)
{
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
}
}
throw new Exception("API呼び出しに失敗しました");
}
このような処理では、Thread.Sleep ではなく Task.Delay を使うべきです。
さらに実運用では、リトライするたびに待機時間を長くする指数バックオフも検討します。
C#var delay = TimeSpan.FromSeconds(Math.Pow(2, attempt));
await Task.Delay(delay, cancellationToken);
7. C#のSleepでよくあるエラー・疑問
7-1. Thread.Sleepが効かないように見える原因
Thread.Sleep が効かないように見える場合、いくつかの原因が考えられます。
まず、待機時間の単位を間違えているケースです。
C#Thread.Sleep(1);
これは1秒ではなく1ミリ秒です。1秒待ちたい場合は次のように書きます。
C#Thread.Sleep(1000);
また、別スレッドで処理が動いている場合、現在のスレッドだけをSleepしても、他のスレッドの処理は止まりません。
Thread.Sleep はアプリ全体を停止するものではなく、呼び出したスレッドだけを停止するものです。
7-2. Task.Delayで待機されない原因
Task.Delay で待機されない原因として最も多いのは、await を付け忘れていることです。
悪い例は次のとおりです。
C#Task.Delay(1000);
Console.WriteLine("すぐに実行される");
このコードでは、Task.Delay(1000) の完了を待っていないため、次の行がすぐに実行されます。
正しくは次のように書きます。
C#await Task.Delay(1000);
Console.WriteLine("1秒後に実行される");
Task.Delay は「待機する処理を表すTask」を返します。待機したいなら、基本的に await が必要です。
7-3. awaitを付け忘れるとどうなるか
await を付け忘れると、待機処理が開始されても、その完了を待たずに次の処理へ進みます。
C#async Task SampleAsync()
{
Task.Delay(1000);
Console.WriteLine("すぐ表示される");
}
このコードでは、1秒待ってから表示されるわけではありません。
正しくは次のとおりです。
C#async Task SampleAsync()
{
await Task.Delay(1000);
Console.WriteLine("1秒後に表示される");
}
また、非同期メソッド自体を呼び出す側でも await を忘れないようにしましょう。
C#await SampleAsync();
非同期処理では、「Taskを作ること」と「Taskの完了を待つこと」は別です。この違いを理解することが重要です。
7-4. Sleep中に処理をキャンセルできるのか
Thread.Sleep は、基本的に指定時間が経過するまで戻ってきません。
そのため、キャンセル可能な待機処理には向いていません。
一方、Task.Delay は CancellationToken を渡すことでキャンセルできます。
C#await Task.Delay(5000, cancellationToken);
キャンセルされると、TaskCanceledException が発生します。
C#try
{
await Task.Delay(5000, cancellationToken);
}
catch (TaskCanceledException)
{
Console.WriteLine("待機がキャンセルされました");
}
キャンセル可能な設計にしたい場合は、Thread.Sleep ではなく Task.Delay を使いましょう。
7-5. 秒・ミリ秒の指定を間違えやすいポイント
C#のSleepで特に多いミスが、秒とミリ秒の混同です。
C#Thread.Sleep(10);
これは10秒ではなく、10ミリ秒です。
10秒待ちたい場合は次のように書きます。
C#Thread.Sleep(10000);
または、TimeSpan を使うと間違いにくくなります。
C#Thread.Sleep(TimeSpan.FromSeconds(10));
Task.Delay でも同じです。
C#await Task.Delay(TimeSpan.FromSeconds(10));
可読性を高めたい場合は、ミリ秒の数値を直接書くよりも TimeSpan を使うのがおすすめです。
7-6. UnityでC#のSleepを使う場合の注意点
UnityでもC#を書くため、Thread.Sleep を使うこと自体は可能です。
しかし、Unityのメインスレッドで Thread.Sleep を使うと、ゲーム全体の描画や入力処理が止まってしまいます。
たとえば、Updateメソッド内で次のようなコードを書くのは避けるべきです。
C#void Update()
{
Thread.Sleep(1000);
}
このようなコードは、フレーム更新を止めてしまい、ゲームが固まったように見える原因になります。
Unityで一定時間待ちたい場合は、通常コルーチンを使います。
C#IEnumerator WaitSample()
{
Debug.Log("開始");
yield return new WaitForSeconds(1f);
Debug.Log("1秒後");
}
Unityでは、C#の一般的な Thread.Sleep よりも、Unityのフレームワークに合った待機方法を使うことが重要です。
8. Sleepの代替手段とより安全な待機処理
8-1. Timerを使って定期実行する方法
一定時間ごとに処理を実行したい場合、Thread.Sleep を使ったループよりもTimerを使うほうが適していることがあります。
たとえば、System.Threading.Timer を使うと、一定間隔でコールバックを実行できます。
C#using System;
using System.Threading;
class Program
{
static void Main()
{
using var timer = new Timer(_ =>
{
Console.WriteLine("定期実行");
}, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
Console.ReadLine();
}
}
このコードでは、1秒ごとに処理が実行されます。
単純に「一定間隔で何かを実行したい」という目的なら、Sleepを使った無限ループよりTimerのほうが自然な場合があります。
8-2. PeriodicTimerを使う方法
.NETでは、非同期の定期実行に PeriodicTimer を使うこともできます。
C#using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(1));
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
try
{
while (await timer.WaitForNextTickAsync(cts.Token))
{
Console.WriteLine("1秒ごとに実行");
}
}
catch (OperationCanceledException)
{
Console.WriteLine("終了");
}
}
}
PeriodicTimer は、非同期処理の中で一定間隔の処理を書くときに便利です。
while ループの中で Task.Delay を使う方法もありますが、定期実行という意図を明確にしたい場合は PeriodicTimer も選択肢になります。
8-3. async/awaitで自然に待機処理を書く方法
非同期処理では、async/await を使って自然に待機処理を書くのが基本です。
C#async Task ExecuteAsync()
{
await Step1Async();
await Task.Delay(TimeSpan.FromSeconds(1));
await Step2Async();
}
このように書くと、同期処理に近い読みやすさを保ちながら、スレッドをブロックしにくいコードになります。
Task.Delay は、非同期処理の流れの中で「少し待ってから次へ進む」ための標準的な方法です。
ただし、単に待てば解決するという設計に頼りすぎないことも大切です。可能であれば、処理完了の通知やイベントを待つ設計にしましょう。
8-4. イベント駆動でSleep自体を不要にする考え方
Sleepを使いたくなる場面の中には、本来Sleepが不要なケースもあります。
たとえば、「ファイルが作成されるまで待つ」という処理で、毎秒Sleepしながら確認する方法があります。
C#while (!File.Exists(path))
{
await Task.Delay(1000);
}
この方法はポーリングです。
しかし、場合によっては FileSystemWatcher のようなイベント通知を使うことで、ファイルが作成されたタイミングで処理を実行できます。
また、UI操作、ネットワーク応答、メッセージ受信なども、イベントやコールバックで処理できることがあります。
Sleepは「時間が経ったら進む」という制御です。一方、イベント駆動は「必要なことが起きたら進む」という制御です。
本番コードでは、できるだけイベント駆動や非同期完了通知を活用し、不要なSleepを減らすことが重要です。
8-5. SpinWaitやManualResetEventSlimとの違い
C#には、待機に関連する仕組みとして SpinWait や ManualResetEventSlim もあります。
SpinWait は、短時間だけCPUを使いながら待機するための仕組みです。非常に短い待機や低レベルな同期処理で使われることがあります。
C#SpinWait.SpinUntil(() => condition);
ただし、通常のアプリケーションで「1秒待つ」ために使うものではありません。
ManualResetEventSlim は、別スレッドからの通知を待つための同期プリミティブです。
C#var resetEvent = new ManualResetEventSlim(false);
resetEvent.Wait();
これは「一定時間待つ」というより、「何かの合図が来るまで待つ」ために使います。
つまり、用途は次のように分かれます。
| 方法 | 主な用途 |
|---|---|
| Thread.Sleep | 現在のスレッドを一定時間止める |
| Task.Delay | 非同期的に一定時間待つ |
| Timer | 一定間隔で処理を実行する |
| PeriodicTimer | 非同期で定期実行する |
| SpinWait | 非常に短い待機や低レベル同期 |
| ManualResetEventSlim | 別スレッドからの通知を待つ |
単に待機したいなら Thread.Sleep または Task.Delay、非同期なら Task.Delay、定期実行ならTimer系を検討するとよいでしょう。
9. C#のSleepに関するベストプラクティス
9-1. UIスレッドではThread.Sleepを使わない
WPF、WinForms、MAUIなどのUIアプリでは、UIスレッドで Thread.Sleep を使わないことが重要です。
UIスレッドは、画面の描画、ユーザー入力、イベント処理などを担当しています。ここで Thread.Sleep を呼ぶと、画面が固まったようになります。
避けるべきコードは次のとおりです。
C#Thread.Sleep(3000);
UIイベント内では、次のように書きましょう。
C#await Task.Delay(3000);
これにより、待機中もUIの応答性を保ちやすくなります。
9-2. 非同期処理ではTask.Delayを基本にする
非同期メソッド内では、Thread.Sleep ではなく Task.Delay を使うのが基本です。
悪い例です。
C#async Task SampleAsync()
{
Thread.Sleep(1000);
await DoSomethingAsync();
}
良い例です。
C#async Task SampleAsync()
{
await Task.Delay(1000);
await DoSomethingAsync();
}
非同期処理の目的は、待機中にスレッドを効率よく使えるようにすることです。
そこで Thread.Sleep を使ってしまうと、非同期処理のメリットを弱めてしまいます。
9-3. 待機時間をマジックナンバーにしない
待機時間をコード中に直接書くと、意味がわかりにくくなります。
C#await Task.Delay(3000);
このコードだけを見ると、3000が何を意味しているのかすぐにわからないことがあります。
定数や TimeSpan を使うと、意図が明確になります。
C#var retryDelay = TimeSpan.FromSeconds(3);
await Task.Delay(retryDelay);
または、定数として定義します。
C#private static readonly TimeSpan RetryDelay = TimeSpan.FromSeconds(3);
await Task.Delay(RetryDelay);
可読性と保守性を高めるために、待機時間には意味のある名前を付けましょう。
9-4. キャンセル可能な待機処理にする
本番コードでは、待機処理をキャンセルできるようにしておくと安全です。
特に、長時間待機、リトライ処理、ポーリング処理、バックグラウンド処理では、キャンセルできない待機は終了処理の妨げになります。
C#await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);
キャンセル可能にしておけば、アプリ終了時や処理中断時にすばやく待機を解除できます。
C#while (!cancellationToken.IsCancellationRequested)
{
await DoWorkAsync(cancellationToken);
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
}
このように、CancellationToken と組み合わせた Task.Delay は、安全な待機処理を書くうえで非常に重要です。
9-5. リトライ処理では指数バックオフを検討する
API呼び出しやネットワーク処理のリトライでは、毎回同じ間隔で再試行するより、徐々に待機時間を延ばす指数バックオフが有効な場合があります。
単純な固定待機は次のようになります。
C#await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
指数バックオフでは、試行回数に応じて待機時間を増やします。
C#var delay = TimeSpan.FromSeconds(Math.Pow(2, retryCount));
await Task.Delay(delay, cancellationToken);
たとえば、1回目は2秒、2回目は4秒、3回目は8秒のように待機時間を長くします。
これにより、一時的な障害時に外部サービスへ過剰なリクエストを送ることを避けやすくなります。
実運用では、最大待機時間の制限やランダムな揺らぎを加えることもあります。
まとめ
C#でSleepにあたる待機処理を行う方法には、主に Thread.Sleep と Task.Delay があります。
Thread.Sleep は、現在のスレッドを指定時間だけ停止する同期的な方法です。コンソールアプリや簡単なサンプル、デバッグ用途では使いやすい一方、UIアプリやWebアプリで安易に使うと、画面のフリーズやパフォーマンス低下の原因になります。
一方、Task.Delay は、非同期的に待機するための方法です。async/await と組み合わせることで、スレッドをブロックしにくく、UIアプリ、ASP.NET、リトライ処理、ポーリング処理などに適しています。
基本的な使い分けは次のとおりです。
| 目的 | 推奨される方法 |
|---|---|
| 同期処理で簡単に待つ | Thread.Sleep |
| 非同期処理で待つ | Task.Delay |
| UIを固めずに待つ | Task.Delay |
| Webアプリで待つ | Task.Delay |
| キャンセル可能にする | Task.Delay + CancellationToken |
| 定期実行する | Timer / PeriodicTimer |
C#で「csharp sleep」や「C# Sleep」を調べている場合、単に Thread.Sleep(1000) の書き方を覚えるだけでなく、スレッドをブロックするかどうかを理解することが重要です。
本番コードでは、まず Task.Delay を検討し、必要に応じて CancellationToken、Timer、イベント駆動などの代替手段を使うことで、より安全で保守しやすい待機処理を書けます。

