using System.Threading; using System.Threading.Tasks; namespace Umbraco.Web.Scheduling { /// /// Provides a base class for recurring background tasks. /// internal abstract class RecurringTaskBase : LatchedBackgroundTaskBase { private readonly IBackgroundTaskRunner _runner; private readonly int _periodMilliseconds; private readonly Timer _timer; private bool _disposed; /// /// 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 override void Run() { var shouldRepeat = PerformRun(); if (shouldRepeat) Repeat(); } /// /// Implements IBackgroundTask.RunAsync(). /// /// Classes inheriting from RecurringTaskBase must implement PerformRun. public 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 abstract bool PerformRun(); /// /// 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 abstract Task PerformRunAsync(CancellationToken token); protected override void DisposeResources() { base.DisposeResources(); // stop the timer _timer.Change(Timeout.Infinite, Timeout.Infinite); _timer.Dispose(); } } }