Skip to content

Instantly share code, notes, and snippets.

@randyburden
Last active February 14, 2023 18:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save randyburden/fe0febe315d7887b2471c7328a95fe5e to your computer and use it in GitHub Desktop.
Save randyburden/fe0febe315d7887b2471c7328a95fe5e to your computer and use it in GitHub Desktop.
Wraps System.Threading.Timer and guarantees non-overlapping executions.
using System;
using System.Threading;
namespace Helpers
{
///<summary>
/// Provides a mechanism for executing a method at specified intervals without overlapping executions.
/// While the timer is executing the method/action, if another interval run occurs, it will return
/// immediately (skipping the new run) to prevent an overlapping execution and allow the previous execution to continue.
/// </summary>
public class NonOverlappingTimer : IDisposable
{
private readonly Timer _timer;
private readonly object _lock = new object();
/// <summary>Initializes a new instance of the Timer class, using <see cref="T:System.TimeSpan" /> values to measure time intervals.</summary>
/// <param name="action">The action to execute at the defined interval. </param>
/// <param name="dueTime">The amount of time to delay before invoking the action. Specify negative one (-1) milliseconds to prevent the timer from starting. Specify zero (0) to start the timer immediately. </param>
/// <param name="period">The time interval between invocations of the action referenced by <paramref name="action" />. Specify negative one (-1) milliseconds to disable periodic signaling. </param>
/// <exception cref="T:System.ArgumentOutOfRangeException">The number of milliseconds in the value of <paramref name="dueTime" /> or <paramref name="period" /> is negative and not equal to <see cref="F:System.Threading.Timeout.Infinite" />, or is greater than <see cref="F:System.Int32.MaxValue" />. </exception>
/// <exception cref="T:System.ArgumentNullException">The <paramref name="action" /> parameter is null. </exception>
public NonOverlappingTimer(Action action, TimeSpan dueTime, TimeSpan period)
{
if (action == null)
{
throw new ArgumentNullException(nameof(action));
}
TimerCallback callback = state =>
{
action();
};
var wrappedCallback = WrapCallback(callback);
_timer = new Timer(wrappedCallback, null, dueTime, period);
}
private TimerCallback WrapCallback(TimerCallback callback)
{
TimerCallback wrappedCallback = state =>
{
// Overlapping calls will not acquire the lock and return immediately
if (!Monitor.TryEnter(_lock))
{
return;
}
try
{
callback(state);
}
catch(Exception)
{
// Ignore
}
finally
{
Monitor.Exit(_lock);
}
};
return wrappedCallback;
}
/// <summary>Initializes a new instance of the Timer class, using a 32-bit signed integer to specify the time interval.</summary>
/// <param name="callback">A <see cref="T:System.Threading.TimerCallback" /> delegate representing a method to be executed. </param>
/// <param name="state">An object containing information to be used by the callback method, or null. </param>
/// <param name="dueTime">The amount of time to delay before <paramref name="callback" /> is invoked, in milliseconds. Specify <see cref="F:System.Threading.Timeout.Infinite" /> to prevent the timer from starting. Specify zero (0) to start the timer immediately. </param>
/// <param name="period">The time interval between invocations of <paramref name="callback" />, in milliseconds. Specify <see cref="F:System.Threading.Timeout.Infinite" /> to disable periodic signaling. </param>
/// <exception cref="T:System.ArgumentOutOfRangeException">The <paramref name="dueTime" /> or <paramref name="period" /> parameter is negative and is not equal to <see cref="F:System.Threading.Timeout.Infinite" />. </exception>
/// <exception cref="T:System.ArgumentNullException">The <paramref name="callback" /> parameter is null. </exception>
public NonOverlappingTimer(TimerCallback callback, object state, int dueTime, int period)
{
var wrappedCallback = WrapCallback(callback);
_timer = new Timer(wrappedCallback, state, dueTime, period);
}
/// <summary>Initializes a new instance of the Timer class, using <see cref="T:System.TimeSpan" /> values to measure time intervals.</summary>
/// <param name="callback">A delegate representing a method to be executed. </param>
/// <param name="state">An object containing information to be used by the callback method, or null. </param>
/// <param name="dueTime">The amount of time to delay before the <paramref name="callback" /> parameter invokes its methods. Specify negative one (-1) milliseconds to prevent the timer from starting. Specify zero (0) to start the timer immediately. </param>
/// <param name="period">The time interval between invocations of the methods referenced by <paramref name="callback" />. Specify negative one (-1) milliseconds to disable periodic signaling. </param>
/// <exception cref="T:System.ArgumentOutOfRangeException">The number of milliseconds in the value of <paramref name="dueTime" /> or <paramref name="period" /> is negative and not equal to <see cref="F:System.Threading.Timeout.Infinite" />, or is greater than <see cref="F:System.Int32.MaxValue" />. </exception>
/// <exception cref="T:System.ArgumentNullException">The <paramref name="callback" /> parameter is null. </exception>
public NonOverlappingTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period)
{
var wrappedCallback = WrapCallback(callback);
_timer = new Timer(wrappedCallback, state, dueTime, period);
}
/// <summary>Initializes a new instance of the Timer class.</summary>
/// <param name="callback">A <see cref="T:System.Threading.TimerCallback" /> delegate representing a method to be executed. </param>
/// <param name="state">An object containing information to be used by the callback method, or null. </param>
/// <param name="dueTime">The amount of time to delay before <paramref name="callback" /> is invoked, in milliseconds. Specify <see cref="F:System.Threading.Timeout.Infinite" /> to prevent the timer from starting. Specify zero (0) to start the timer immediately. </param>
/// <param name="period">The time interval between invocations of <paramref name="callback" />, in milliseconds. Specify <see cref="F:System.Threading.Timeout.Infinite" /> to disable periodic signaling. </param>
/// <exception cref="T:System.ArgumentOutOfRangeException">The <paramref name="dueTime" /> or <paramref name="period" /> parameter is negative and is not equal to <see cref="F:System.Threading.Timeout.Infinite" />. </exception>
/// <exception cref="T:System.ArgumentNullException">The <paramref name="callback" /> parameter is null. </exception>
public NonOverlappingTimer(TimerCallback callback, object state, uint dueTime, uint period)
{
var wrappedCallback = WrapCallback(callback);
_timer = new Timer(wrappedCallback, state, dueTime, period);
}
/// <summary>Initializes a new instance of the Timer class.</summary>
/// <param name="callback">A <see cref="T:System.Threading.TimerCallback" /> delegate representing a method to be executed. </param>
/// <param name="state">An object containing information to be used by the callback method, or null. </param>
/// <param name="dueTime">The amount of time to delay before <paramref name="callback" /> is invoked, in milliseconds. Specify <see cref="F:System.Threading.Timeout.Infinite" /> to prevent the timer from starting. Specify zero (0) to start the timer immediately. </param>
/// <param name="period">The time interval between invocations of <paramref name="callback" />, in milliseconds. Specify <see cref="F:System.Threading.Timeout.Infinite" /> to disable periodic signaling. </param>
/// <exception cref="T:System.ArgumentOutOfRangeException">The <paramref name="dueTime" /> or <paramref name="period" /> parameter is negative and is not equal to <see cref="F:System.Threading.Timeout.Infinite" />. </exception>
/// <exception cref="T:System.ArgumentNullException">The <paramref name="callback" /> parameter is null. </exception>
public NonOverlappingTimer(TimerCallback callback, object state, long dueTime, long period)
{
var wrappedCallback = WrapCallback(callback);
_timer = new Timer(wrappedCallback, state, dueTime, period);
}
/// <summary>Initializes a new instance of the Timer class.</summary>
/// <param name="callback">A <see cref="T:System.Threading.TimerCallback" /> delegate representing a method to be executed. </param>
public NonOverlappingTimer(TimerCallback callback)
{
var wrappedCallback = WrapCallback(callback);
_timer = new Timer(wrappedCallback);
}
/// <summary>Changes the start time and the interval between method invocations for a timer, using 32-bit signed integers to measure time intervals.</summary>
/// <returns>true if the timer was successfully updated; otherwise, false.</returns>
/// <param name="dueTime">The amount of time to delay before the invoking the callback method specified when the <see cref="T:System.Threading.Timer" /> was constructed, in milliseconds. Specify <see cref="F:System.Threading.Timeout.Infinite" /> to prevent the timer from restarting. Specify zero (0) to restart the timer immediately. </param>
/// <param name="period">The time interval between invocations of the callback method specified when the <see cref="T:System.Threading.Timer" /> was constructed, in milliseconds. Specify <see cref="F:System.Threading.Timeout.Infinite" /> to disable periodic signaling. </param>
/// <exception cref="T:System.ObjectDisposedException">The <see cref="T:System.Threading.Timer" /> has already been disposed. </exception>
/// <exception cref="T:System.ArgumentOutOfRangeException">The <paramref name="dueTime" /> or <paramref name="period" /> parameter is negative and is not equal to <see cref="F:System.Threading.Timeout.Infinite" />. </exception>
/// <filterpriority>2</filterpriority>
public bool Change(int dueTime, int period)
{
return _timer.Change(dueTime, period);
}
/// <summary>Changes the start time and the interval between method invocations for a timer.</summary>
/// <returns>true if the timer was successfully updated; otherwise, false.</returns>
/// <param name="dueTime">The amount of time to delay before the invoking the callback method specified when the <see cref="T:System.Threading.Timer" /> was constructed, in milliseconds. Specify <see cref="F:System.Threading.Timeout.Infinite" /> to prevent the timer from restarting. Specify zero (0) to restart the timer immediately. </param>
/// <param name="period">The time interval between invocations of the callback method specified when the <see cref="T:System.Threading.Timer" /> was constructed, in milliseconds. Specify <see cref="F:System.Threading.Timeout.Infinite" /> to disable periodic signaling. </param>
/// <exception cref="T:System.ObjectDisposedException">The <see cref="T:System.Threading.Timer" /> has already been disposed. </exception>
/// <exception cref="T:System.ArgumentOutOfRangeException">The <paramref name="dueTime" /> or <paramref name="period" /> parameter is negative and is not equal to <see cref="F:System.Threading.Timeout.Infinite" />. </exception>
/// <filterpriority>2</filterpriority>
public bool Change(TimeSpan dueTime, TimeSpan period)
{
return _timer.Change(dueTime, period);
}
/// <summary>Changes the start time and the interval between method invocations for a timer.</summary>
/// <returns>true if the timer was successfully updated; otherwise, false.</returns>
/// <param name="dueTime">The amount of time to delay before the invoking the callback method specified when the <see cref="T:System.Threading.Timer" /> was constructed, in milliseconds. Specify <see cref="F:System.Threading.Timeout.Infinite" /> to prevent the timer from restarting. Specify zero (0) to restart the timer immediately. </param>
/// <param name="period">The time interval between invocations of the callback method specified when the <see cref="T:System.Threading.Timer" /> was constructed, in milliseconds. Specify <see cref="F:System.Threading.Timeout.Infinite" /> to disable periodic signaling. </param>
/// <exception cref="T:System.ObjectDisposedException">The <see cref="T:System.Threading.Timer" /> has already been disposed. </exception>
/// <exception cref="T:System.ArgumentOutOfRangeException">The <paramref name="dueTime" /> or <paramref name="period" /> parameter is negative and is not equal to <see cref="F:System.Threading.Timeout.Infinite" />. </exception>
/// <filterpriority>2</filterpriority>
public bool Change(uint dueTime, uint period)
{
return _timer.Change(dueTime, period);
}
/// <summary>Changes the start time and the interval between method invocations for a timer.</summary>
/// <returns>true if the timer was successfully updated; otherwise, false.</returns>
/// <param name="dueTime">The amount of time to delay before the invoking the callback method specified when the <see cref="T:System.Threading.Timer" /> was constructed, in milliseconds. Specify <see cref="F:System.Threading.Timeout.Infinite" /> to prevent the timer from restarting. Specify zero (0) to restart the timer immediately. </param>
/// <param name="period">The time interval between invocations of the callback method specified when the <see cref="T:System.Threading.Timer" /> was constructed, in milliseconds. Specify <see cref="F:System.Threading.Timeout.Infinite" /> to disable periodic signaling. </param>
/// <exception cref="T:System.ObjectDisposedException">The <see cref="T:System.Threading.Timer" /> has already been disposed. </exception>
/// <exception cref="T:System.ArgumentOutOfRangeException">The <paramref name="dueTime" /> or <paramref name="period" /> parameter is negative and is not equal to <see cref="F:System.Threading.Timeout.Infinite" />. </exception>
/// <filterpriority>2</filterpriority>
public bool Change(long dueTime, long period)
{
return _timer.Change(dueTime, dueTime);
}
/// <summary>
/// Stop the timer.
/// </summary>
public void Stop()
{
_timer.Change(Timeout.Infinite, Timeout.Infinite);
}
/// <summary>Releases all resources used by the current instance of <see cref="T:System.Threading.Timer" />.</summary>
/// <filterpriority>2</filterpriority>
public bool Dispose(WaitHandle notifyObject)
{
return _timer.Dispose(notifyObject);
}
/// <summary>Releases all resources used by the current instance of <see cref="T:System.Threading.Timer" />.</summary>
/// <filterpriority>2</filterpriority>
public void Dispose()
{
_timer.Dispose();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment