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; } } } } }