Created
August 16, 2019 12:12
-
-
Save abdurrahman/3ffe2b44f48f9b28d83d71f22de932f0 to your computer and use it in GitHub Desktop.
Managing multiple Timer instances
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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