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 optionnally override
/// RunsOnShutdown, in order to indicate whether the latched task should run immediately on
/// shutdown, or just be abandonned (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();
}
}
}