using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; namespace Swan.Threading { /// /// 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(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; } } } } } }