using System; using System.Threading; using System.Threading.Tasks; namespace Umbraco.Web.Scheduling { /// /// Provides a base class for recurring background tasks. /// /// Implement by overriding PerformRun or PerformRunAsync and then IsAsync accordingly, /// depending on whether the task is implemented as a sync or async method. Run nor RunAsync are /// sealed here as overriding them would break recurrence. And then optionally override /// RunsOnShutdown, in order to indicate whether the latched task should run immediately on /// shutdown, or just be abandoned (default). public abstract class RecurringTaskBase : LatchedBackgroundTaskBase { private readonly IBackgroundTaskRunner _runner; private readonly int _periodMilliseconds; private readonly Timer _timer; /// /// Initializes a new instance of the class. /// /// The task runner. /// The delay. /// The period. /// The task will repeat itself periodically. Use this constructor to create a new task. protected RecurringTaskBase(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds) { _runner = runner; _periodMilliseconds = periodMilliseconds; // note // must use the single-parameter constructor on Timer to avoid it from being GC'd // read http://stackoverflow.com/questions/4962172/why-does-a-system-timers-timer-survive-gc-but-not-system-threading-timer _timer = new Timer(_ => Release()); _timer.Change(delayMilliseconds, 0); } /// /// Implements IBackgroundTask.Run(). /// /// Classes inheriting from RecurringTaskBase must implement PerformRun. public sealed override void Run() { var shouldRepeat = PerformRun(); if (shouldRepeat) Repeat(); } /// /// Implements IBackgroundTask.RunAsync(). /// /// Classes inheriting from RecurringTaskBase must implement PerformRun. public sealed override async Task RunAsync(CancellationToken token) { var shouldRepeat = await PerformRunAsync(token); if (shouldRepeat) Repeat(); } private void Repeat() { // again? if (_runner.IsCompleted) return; // fail fast if (_periodMilliseconds == 0) return; // safe Reset(); // re-latch // try to add again (may fail if runner has completed) // if added, re-start the timer, else kill it if (_runner.TryAdd(this)) _timer.Change(_periodMilliseconds, 0); else Dispose(); } /// /// Runs the background task. /// /// A value indicating whether to repeat the task. public virtual bool PerformRun() { throw new NotSupportedException("This task cannot run synchronously."); } /// /// Runs the task asynchronously. /// /// A cancellation token. /// A instance representing the execution of the background task, /// and returning a value indicating whether to repeat the task. public virtual Task PerformRunAsync(CancellationToken token) { throw new NotSupportedException("This task cannot run asynchronously."); } protected override void DisposeResources() { base.DisposeResources(); // stop the timer _timer.Change(Timeout.Infinite, Timeout.Infinite); _timer.Dispose(); } } }