RaspberryIO_26/Swan/Threading/WorkerDelayProvider.cs

147 lines
4.6 KiB
C#
Raw Normal View History

2019-12-08 21:23:54 +01:00
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace Swan.Threading {
/// <summary>
/// Represents a class that implements delay logic for thread workers.
/// </summary>
public static class WorkerDelayProvider {
2019-12-04 18:57:18 +01:00
/// <summary>
2019-12-08 21:23:54 +01:00
/// Gets the default delay provider.
2019-12-04 18:57:18 +01:00
/// </summary>
2019-12-08 21:23:54 +01:00
public static IWorkerDelayProvider Default => TokenTimeout;
/// <summary>
/// Provides a delay implementation which simply waits on the task and cancels on
/// the cancellation token.
/// </summary>
public static IWorkerDelayProvider Token => new TokenCancellableDelay();
/// <summary>
/// Provides a delay implementation which waits on the task and cancels on both,
/// the cancellation token and a wanted delay timeout.
/// </summary>
public static IWorkerDelayProvider TokenTimeout => new TokenTimeoutCancellableDelay();
/// <summary>
/// Provides a delay implementation which uses short sleep intervals of 5ms.
/// </summary>
public static IWorkerDelayProvider TokenSleep => new TokenSleepDelay();
/// <summary>
/// Provides a delay implementation which uses short delay intervals of 5ms and
/// a wait on the delay task in the final loop.
/// </summary>
public static IWorkerDelayProvider SteppedToken => new SteppedTokenDelay();
private class TokenCancellableDelay : IWorkerDelayProvider {
public void ExecuteCycleDelay(Int32 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(Int32 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(Int32 wantedDelay, Task delayTask, CancellationToken token) {
this._elapsedWait.Restart();
if(wantedDelay == 0 || wantedDelay < -1) {
return;
}
while(!token.IsCancellationRequested) {
Thread.Sleep(5);
if(wantedDelay != Timeout.Infinite && this._elapsedWait.ElapsedMilliseconds >= wantedDelay) {
break;
}
}
}
}
private class SteppedTokenDelay : IWorkerDelayProvider {
private const Int32 StepMilliseconds = 15;
private readonly Stopwatch _elapsedWait = new Stopwatch();
public void ExecuteCycleDelay(Int32 wantedDelay, Task delayTask, CancellationToken token) {
this._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) {
Int32 remainingWaitTime = wantedDelay - Convert.ToInt32(this._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(this._elapsedWait.ElapsedMilliseconds >= wantedDelay) {
break;
}
}
}
}
}
2019-12-04 18:57:18 +01:00
}