Skip to content

Instantly share code, notes, and snippets.

@abdurrahman
Created August 16, 2019 12:12
Show Gist options
  • Save abdurrahman/3ffe2b44f48f9b28d83d71f22de932f0 to your computer and use it in GitHub Desktop.
Save abdurrahman/3ffe2b44f48f9b28d83d71f22de932f0 to your computer and use it in GitHub Desktop.
Managing multiple Timer instances
using System;
namespace Core.Interfaces
{
/// <summary>
/// An helper to manage generated events after a set interval, due time
/// with an option to generate recurring events.
/// </summary>
public interface ITimerHelper
{
/// <summary>
/// Starts raising the Action event by setting System.Threading.Tasks.
/// </summary>
/// <param name="timerId">Defines task id for managing in Dictionary</param>
/// <param name="action">Delegate representing a method to be executed</param>
/// <param name="interval">The time interval between invocations of the callback method specified
/// in milliseconds.</param>
/// <param name="dueTime">The amount of time to end after the invoking the callback method specified
// in DateTime.Utc.</param>
void Start(string timerId, Action action, double interval, DateTime? dueTime = null);
/// <summary>
/// Stops raising the Action event by setting CancellationToken to false.
/// </summary>
/// <param name="timerId">Defines task id for managing in Dictionary</param>
void Stop(string timerId);
/// <summary>
/// Updates the due time and the interval between method invocations for a Task,
/// using DateTime values to measure time intervals.
/// </summary>
/// <param name="timerId">Defines task id for managing in Dictionary</param>
/// <param name="action">Representing a method to be executed</param>
/// <param name="interval">The time interval between invocations of the callback method specified
/// in milliseconds.</param>
/// <param name="dueTime">The amount of time to complete after the invoking the callback
/// method specified in DateTime.Utc.</param>
void Update(string timerId, Action action, double? interval = null, DateTime? dueTime = null);
}
public class TimerHelper : ITimerHelper
{
// List to keep track of created instances.
private readonly ConcurrentDictionary<string, TimerInstance> _timerInstances;
public TimerHelper()
{
_timerInstances = new ConcurrentDictionary<string, TimerInstance>();
}
public void Start(string timerId, Action action, double interval, DateTime? endValue = null)
{
var dueTime = endValue ?? DateTime.MaxValue;
var timerInstance = new TimerInstance
{
DueTime = dueTime,
Interval = interval
};
// Set timer instance with a Task Runner.
timerInstance.Task = ExecuteTask(action, timerInstance);
// Add the instance to the dictionary of instances.
_timerInstances.TryAdd(timerId, timerInstance);
}
public void Stop(string timerId)
{
if (_timerInstances.TryRemove(timerId, out var timerInstance))
{
timerInstance.CancellationToken.Cancel();
}
}
public void Update(string timerId, Action action, double? interval = null, DateTime? dueTime = null)
{
if (!_timerInstances.TryGetValue(timerId, out var timerInstance))
{
return;
}
var oldDueTime = timerInstance.DueTime;
var oldInterval = timerInstance.Interval;
timerInstance.DueTime = dueTime ?? timerInstance.DueTime;
timerInstance.Interval = interval ?? timerInstance.Interval;
if ((interval == null || interval.Value == oldInterval) && (!dueTime.HasValue || oldDueTime < dueTime.Value))
{
return;
}
// Cancel the task before update execution.
timerInstance.CancellationToken.Cancel();
timerInstance.Task = ExecuteTask(action, timerInstance, async () =>
{
await Task.Delay(TimeSpan.FromMilliseconds(timerInstance.Interval - (DateTime.UtcNow - timerInstance.StartDate).TotalMilliseconds), timerInstance.CancellationToken.Token);
action();
});
}
private Task ExecuteTask(Action action, TimerInstance timerInstance, Action preTask = null)
{
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
timerInstance.CancellationToken = tokenSource;
return Task.Run(async () =>
{
preTask?.Invoke();
while (true)
{
timerInstance.StartDate = DateTime.UtcNow;
if (DateTime.UtcNow.AddMilliseconds(timerInstance.Interval) > timerInstance.DueTime)
break;
await Task.Delay(TimeSpan.FromMilliseconds(timerInstance.Interval), token);
action();
}
}, token);
}
}
public class TimerInstance
{
public Task Task { get; set; }
public DateTime DueTime { get; set; }
public double Interval { get; set; }
public DateTime StartDate { get; set; }
public CancellationTokenSource CancellationToken { get; set; }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment