namespace Swan.Threading
{
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
///
/// Represents a class that implements delay logic for thread workers.
///
public static class WorkerDelayProvider
{
///
/// Gets the default delay provider.
///
public static IWorkerDelayProvider Default => TokenTimeout;
///
/// Provides a delay implementation which simply waits on the task and cancels on
/// the cancellation token.
///
public static IWorkerDelayProvider Token => new TokenCancellableDelay();
///
/// Provides a delay implementation which waits on the task and cancels on both,
/// the cancellation token and a wanted delay timeout.
///
public static IWorkerDelayProvider TokenTimeout => new TokenTimeoutCancellableDelay();
///
/// Provides a delay implementation which uses short sleep intervals of 5ms.
///
public static IWorkerDelayProvider TokenSleep => new TokenSleepDelay();
///
/// Provides a delay implementation which uses short delay intervals of 5ms and
/// a wait on the delay task in the final loop.
///
public static IWorkerDelayProvider SteppedToken => new SteppedTokenDelay();
private class TokenCancellableDelay : IWorkerDelayProvider
{
public void ExecuteCycleDelay(int wantedDelay, Task delayTask, CancellationToken token)
{
if (wantedDelay == 0 || wantedDelay < -1)
return;
// for wanted delays of less than 30ms it is not worth
// passing a timeout or a token as it only adds unnecessary
// overhead.
if (wantedDelay <= 30)
{
try { delayTask.Wait(token); }
catch { /* ignore */ }
return;
}
// only wait on the cancellation token
// or until the task completes normally
try { delayTask.Wait(token); }
catch { /* ignore */ }
}
}
private class TokenTimeoutCancellableDelay : IWorkerDelayProvider
{
public void ExecuteCycleDelay(int wantedDelay, Task delayTask, CancellationToken token)
{
if (wantedDelay == 0 || wantedDelay < -1)
return;
// for wanted delays of less than 30ms it is not worth
// passing a timeout or a token as it only adds unnecessary
// overhead.
if (wantedDelay <= 30)
{
try { delayTask.Wait(token); }
catch { /* ignore */ }
return;
}
try { delayTask.Wait(wantedDelay, token); }
catch { /* ignore */ }
}
}
private class TokenSleepDelay : IWorkerDelayProvider
{
private readonly Stopwatch _elapsedWait = new Stopwatch();
public void ExecuteCycleDelay(int wantedDelay, Task delayTask, CancellationToken token)
{
_elapsedWait.Restart();
if (wantedDelay == 0 || wantedDelay < -1)
return;
while (!token.IsCancellationRequested)
{
Thread.Sleep(5);
if (wantedDelay != Timeout.Infinite && _elapsedWait.ElapsedMilliseconds >= wantedDelay)
break;
}
}
}
private class SteppedTokenDelay : IWorkerDelayProvider
{
private const int StepMilliseconds = 15;
private readonly Stopwatch _elapsedWait = new Stopwatch();
public void ExecuteCycleDelay(int wantedDelay, Task delayTask, CancellationToken token)
{
_elapsedWait.Restart();
if (wantedDelay == 0 || wantedDelay < -1)
return;
if (wantedDelay == Timeout.Infinite)
{
try { delayTask.Wait(wantedDelay, token); }
catch { /* Ignore cancelled tasks */ }
return;
}
while (!token.IsCancellationRequested)
{
var remainingWaitTime = wantedDelay - Convert.ToInt32(_elapsedWait.ElapsedMilliseconds);
// Exit for no remaining wait time
if (remainingWaitTime <= 0)
break;
if (remainingWaitTime >= StepMilliseconds)
{
Task.Delay(StepMilliseconds, token).Wait(token);
}
else
{
try { delayTask.Wait(remainingWaitTime); }
catch { /* ignore cancellation of task exception */ }
}
if (_elapsedWait.ElapsedMilliseconds >= wantedDelay)
break;
}
}
}
}
}