How many ways to sleep a thread do you know?
Usually you need to sleep a thread in a loop when you execute some repeatable operation (checking results every 5 seconds, for example). So, you need to stop this loop for a period of time.
Thread.Sleep
The most popular way — use Thread.Sleep
method:
private static void Test1()
{
while (true)
{
Console.WriteLine("Checking...");
if (File.Exists("C:\\1.txt"))
break;
Thread.Sleep(TimeSpan.FromSeconds(5));
}
}
The simplest way, but it has one big defect. Method Sleep
is synchronous and cannot be interrupted. So, you need to wait 5 seconds before exit. To solve this problem, you can use some sort of WaitHandle
.
WaitHandle
private static void Test2()
{
var autoResetEvent = new AutoResetEvent(false);
while (true)
{
Console.WriteLine("Checking...");
if (File.Exists("C:\\1.txt"))
break;
if (autoResetEvent.WaitOne(TimeSpan.FromSeconds(5)))
break;
}
}
Method WaitOne
blocks the current thread until the current instance receives a signal. Argument specifies a period of time to wait the signal. After the period method the thread exit from the method and continue to execute.
EventWaitHandle
(base class for AutoResetEvent
) has method Set
to signal the instance. After receiving this signal AutoResetEvent
immediately frees the thread and the code continues to execute without waiting excessive 5 seconds:
autoResetEvent.Set();
WaitOne
will return true
, it instance get signal.
Task.Delay
Since .net 4, when Microsoft introduces concept of asynchronous operations with Tasks, we get new improved equivalent to Thread.Sleep
— Task.Delay
.
private static async Task Test3()
{
var tokenSource = new CancellationTokenSource();
while (true)
{
Console.WriteLine("Checking...");
if (File.Exists("C:\\1.txt"))
break;
await Task.Delay(TimeSpan.FromSeconds(5), tokenSource.Token);
if (tokenSource.Token.IsCancellationRequested)
break;
}
}
Second argument — CancellationToken
allows us to control execution of Delay
. But unlike WaitHandle
where you can set signal several times, CancellationTokenSource
can cancel Task
only once. Check IsCancellationRequested
property to detect if you need to exit. And the loop can be transformed:
private static async Task Test3()
{
var tokenSource = new CancellationTokenSource();
while (!tokenSource.Token.IsCancellationRequested)
{
Console.WriteLine("Checking...");
if (File.Exists("C:\\1.txt"))
break;
await Task.Delay(TimeSpan.FromSeconds(5), tokenSource.Token);
}
}
CancellationToken
CancellationToken itself contains WaitHandle
and it can be used for delay:
private static void Test4()
{
var tokenSource = new CancellationTokenSource();
while (!tokenSource.Token.IsCancellationRequested)
{
Console.WriteLine("Checking...");
if (File.Exists("C:\\1.txt"))
break;
tokenSource.Token.WaitHandle.WaitOne(TimeSpan.FromSeconds(5));
}
}
Benchmark
Simple benchmark (with Benchmark.NET) to check delay intervals:
namespace SleepWell
{
public class Program
{
private CancellationToken _token = new CancellationToken();
static void Main(string[] args)
{
BenchmarkRunner.Run<Program>();
}
[Benchmark]
public void Benchmark1()
{
Thread.Sleep(TimeSpan.FromMilliseconds(2));
}
[Benchmark]
public async Task Benchmark2()
{
await Task.Delay(TimeSpan.FromMilliseconds(2));
}
[Benchmark]
public void Benchmark3()
{
_token.WaitHandle.WaitOne(TimeSpan.FromMilliseconds(2));
}
}
}
Results:
Method | Mean | Error | StdDev |
----------- |----------:|----------:|----------:|
Benchmark1 | 2.348 ms | 0.0218 ms | 0.0193 ms |
Benchmark2 | 15.614 ms | 0.0199 ms | 0.0186 ms |
Benchmark3 | 2.235 ms | 0.0153 ms | 0.0143 ms |
Old-school Thread.Sleep
and modern CancellationToken
can handle very small interval, while Task.Delay
has limit to about 15ms. The reason — Task.Delay
is build on Timer
that has default resolution 15.6 milliseconds. WaitHandle
and Thread
use native calls to sleep a thread.