using System;
using System.Collections.Generic;
using System.Threading;
namespace Unclassified
{
///
/// Implements a timer that invokes a method after a user-defined timeout.
///
///
/// Project website: http://beta.unclassified.de/code/dotnet/delayedcall/
///
public class DelayedCall : IDisposable
{
// This class maintains compatibility with an older version of the DelayedCall class.
// See the "Compatibility code" regions for details. To remove the compatibility code,
// you just need to remove these regions.
public delegate void Callback();
///
/// List to keep track of all created DelayedCall instances
///
protected static List dcList;
protected System.Timers.Timer timer;
protected object timerLock;
private Callback callback;
protected bool cancelled = false;
protected SynchronizationContext context;
// More information on the SynchronizationContext thing: http://www.codeproject.com/cs/threads/SyncContextTutorial.asp
///
/// Static class constructor, initialising static fields
///
static DelayedCall()
{
dcList = new List();
}
///
/// Private instance constructor, initialising instance fields
///
protected DelayedCall()
{
timerLock = new object();
}
#region Compatibility code
private DelayedCall.Callback oldCallback = null;
private object oldData = null;
[Obsolete("Use the static method DelayedCall.Create instead.")]
public DelayedCall(Callback cb)
: this()
{
PrepareDCObject(this, 0, false);
callback = cb;
}
[Obsolete("Use the static method DelayedCall.Create instead.")]
public DelayedCall(DelayedCall.Callback cb, object data)
: this()
{
PrepareDCObject(this, 0, false);
oldCallback = cb;
oldData = data;
}
[Obsolete("Use the static method DelayedCall.Start instead.")]
public DelayedCall(Callback cb, int milliseconds)
: this()
{
PrepareDCObject(this, milliseconds, false);
callback = cb;
if (milliseconds > 0) Start();
}
[Obsolete("Use the static method DelayedCall.Start instead.")]
public DelayedCall(DelayedCall.Callback cb, int milliseconds, object data)
: this()
{
PrepareDCObject(this, milliseconds, false);
oldCallback = cb;
oldData = data;
if (milliseconds > 0) Start();
}
[Obsolete("Use the method Restart of the generic class instead.")]
public void Reset(object data)
{
Cancel();
oldData = data;
Start();
}
[Obsolete("Use the method Restart of the generic class instead.")]
public void Reset(int milliseconds, object data)
{
Cancel();
oldData = data;
Reset(milliseconds);
}
[Obsolete("Use the method Restart instead.")]
public void SetTimeout(int milliseconds)
{
Reset(milliseconds);
}
#endregion
///
/// Creates a new DelayedCall instance.
///
/// Callback function
/// Time to callback invocation
/// Newly created DelayedCall instance that can be used for later controlling of the invocation process
public static DelayedCall Create(Callback cb, int milliseconds)
{
DelayedCall dc = new DelayedCall();
PrepareDCObject(dc, milliseconds, false);
dc.callback = cb;
return dc;
}
///
/// Creates a new asynchronous DelayedCall instance. The callback function will be invoked on a ThreadPool thread.
///
/// Callback function
/// Time to callback invocation
/// Newly created DelayedCall instance that can be used for later controlling of the invocation process
public static DelayedCall CreateAsync(Callback cb, int milliseconds)
{
DelayedCall dc = new DelayedCall();
PrepareDCObject(dc, milliseconds, true);
dc.callback = cb;
return dc;
}
///
/// Creates and starts a new DelayedCall instance.
///
/// Callback function
/// Time to callback invocation
/// Newly created DelayedCall instance that can be used for later controlling of the invocation process
public static DelayedCall Start(Callback cb, int milliseconds)
{
DelayedCall dc = Create(cb, milliseconds);
if (milliseconds > 0) dc.Start();
else if (milliseconds == 0) dc.FireNow();
return dc;
}
///
/// Creates and starts a new asynchronous DelayedCall instance. The callback function will be invoked on a ThreadPool thread.
///
/// Callback function
/// Time to callback invocation
/// Newly created DelayedCall instance that can be used for later controlling of the invocation process
public static DelayedCall StartAsync(Callback cb, int milliseconds)
{
DelayedCall dc = CreateAsync(cb, milliseconds);
if (milliseconds > 0) dc.Start();
else if (milliseconds == 0) dc.FireNow();
return dc;
}
protected static void PrepareDCObject(DelayedCall dc, int milliseconds, bool async)
{
if (milliseconds < 0)
{
throw new ArgumentOutOfRangeException("milliseconds", "The new timeout must be 0 or greater.");
}
// Get the current synchronization context if required, with dummy fallback
dc.context = null;
if (!async)
{
dc.context = SynchronizationContext.Current;
if (dc.context == null)
throw new InvalidOperationException("Cannot delay calls synchronously on a non-UI thread. Use the *Async methods instead.");
}
if (dc.context == null) dc.context = new SynchronizationContext(); // Run asynchronously silently
dc.timer = new System.Timers.Timer();
if (milliseconds > 0) dc.timer.Interval = milliseconds;
dc.timer.AutoReset = false;
dc.timer.Elapsed += dc.Timer_Elapsed;
Register(dc);
}
protected static void Register(DelayedCall dc)
{
lock (dcList)
{
// Keep a reference on this object to prevent it from being caught by the Garbage Collector
if (!dcList.Contains(dc))
dcList.Add(dc);
}
}
protected static void Unregister(DelayedCall dc)
{
lock (dcList)
{
// Free a reference on this object to allow the Garbage Collector to dispose it
// if no other reference is kept to it
dcList.Remove(dc);
}
}
///
/// Gets the number of currently registered DelayedCall instances.
///
public static int RegisteredCount
{
get
{
lock (dcList)
{
return dcList.Count;
}
}
}
///
/// Gets a value indicating whether any registered DelayedCall instance is still waiting to fire.
///
public static bool IsAnyWaiting
{
get
{
lock (dcList)
{
foreach (DelayedCall dc in dcList)
{
if (dc.IsWaiting) return true;
}
}
return false;
}
}
///
/// Cancels all waiting DelayedCall instances.
///
public static void CancelAll()
{
lock (dcList)
{
foreach (DelayedCall dc in dcList)
{
dc.Cancel();
}
}
}
///
/// Fires all waiting DelayedCall instances.
///
public static void FireAll()
{
lock (dcList)
{
foreach (DelayedCall dc in dcList)
{
dc.Fire();
}
}
}
///
/// Disposes all registered DelayedCall instances. This will empty the list of registered DelayedCall instances.
///
public static void DisposeAll()
{
lock (dcList)
{
// Disposing an object removes it from the list, so foreach doesn't work
while (dcList.Count > 0)
{
dcList[0].Dispose();
}
}
}
protected virtual void Timer_Elapsed(object o, System.Timers.ElapsedEventArgs e)
{
// We're in the timer thread now.
FireNow();
Unregister(this);
}
///
/// Frees all resources of this instance.
///
public void Dispose()
{
Unregister(this);
timer.Dispose();
}
///
/// Starts or restarts the timer.
///
public void Start()
{
lock (timerLock)
{
cancelled = false;
timer.Start();
Register(this);
}
}
///
/// Stops the timer to prevent the callback function to be invoked.
///
public void Cancel()
{
lock (timerLock)
{
cancelled = true;
Unregister(this);
timer.Stop();
}
}
///
/// Gets a value indicating whether the DelayedCall instance is currently waiting to fire.
///
public bool IsWaiting
{
get
{
lock (timerLock)
{
return timer.Enabled && !cancelled;
}
}
}
///
/// Fires the callback event regardless of whether the timeout has already finished.
///
///
/// The event will not be fired when the timeout has finished before, so when calling Fire
/// near the time when the timer would elapse, you won't risk of calling it twice. If you need
/// to invoke the callback function now and the timer has already elapsed, use Reset(0) .
///
public void Fire()
{
lock (timerLock)
{
if (!IsWaiting) return;
timer.Stop();
}
FireNow();
}
///
/// Fires the callback event regardless of whether the timeout has already finished or was started at all.
///
///
/// The event will be fired even when the timeout has finished before. This is the method used by Fire()
/// and it takes care of always unregistering the DelayedCall object from the internal list.
///
public void FireNow()
{
OnFire();
Unregister(this);
}
protected virtual void OnFire()
{
// We're in the timer thread now, if invoked through the timer.
context.Post(delegate
{
// We're in the target thread now.
lock (timerLock)
{
// Only fire the callback if the timer wasn't cancelled in this very moment.
// This check needs to be done inside the lock and in the target thread because
// the invocation in the target thread can be delayed and Cancel() - setting
// cancelled to true - may be called after this delegate has been posted to the
// target thread. So, checking the cancelled variable here leads to a more current
// and definitive decision (the target thread cannot call Cancel() between this
// check and the real invocation of the callback method.)
if (cancelled) return;
}
if (callback != null) callback();
#region Compatibility code
if (oldCallback != null) oldCallback(oldData);
#endregion
}, null);
}
///
/// Resets the timeout and restarts the timer.
///
public void Reset()
{
lock (timerLock)
{
Cancel();
Start();
// TODO: This sets and unsets the cancelled flag, so immediate execution of the
// previous timer right after this method call cannot be eliminated.
}
}
///
/// Resets the timeout to a new value and restarts the timer.
///
/// New timeout value
public void Reset(int milliseconds)
{
lock (timerLock)
{
Cancel();
Milliseconds = milliseconds;
Start();
// TODO: This sets and unsets the cancelled flag, so immediate execution of the
// previous timer right after this method call cannot be eliminated.
// (Derived classes are also affected.)
}
}
///
/// Gets or sets the timeout in milliseconds currently assigned to this DelayedCall instance.
///
///
/// Changing the timeout before it elapsed will restart the timer with the new value.
///
public int Milliseconds
{
get
{
lock (timerLock)
{
return (int) timer.Interval;
}
}
set
{
lock (timerLock)
{
if (value < 0)
{
throw new ArgumentOutOfRangeException("Milliseconds", "The new timeout must be 0 or greater.");
}
else if (value == 0)
{
Cancel();
FireNow();
Unregister(this);
}
else
{
timer.Interval = value;
// TODO: Is this untested?
}
}
}
}
}
///
/// Implements a timer that invokes a method with 1 argument after a user-defined timeout.
///
public class DelayedCall : DelayedCall
{
public new delegate void Callback(T data);
private Callback callback;
private T data;
///
/// Creates a new DelayedCall instance with 1 data argument.
///
/// Callback function
/// Argument for callback function
/// Time to callback invocation
/// Newly created DelayedCall instance that can be used for later controlling of the invocation process
public static DelayedCall Create(Callback cb, T data, int milliseconds)
{
DelayedCall dc = new DelayedCall();
PrepareDCObject(dc, milliseconds, false);
dc.callback = cb;
dc.data = data;
return dc;
}
///
/// Creates a new asynchronous DelayedCall instance with 1 data argument. The callback function will be invoked on a ThreadPool thread.
///
/// Callback function
/// Argument for callback function
/// Time to callback invocation
/// Newly created DelayedCall instance that can be used for later controlling of the invocation process
public static DelayedCall CreateAsync(Callback cb, T data, int milliseconds)
{
DelayedCall dc = new DelayedCall();
PrepareDCObject(dc, milliseconds, true);
dc.callback = cb;
dc.data = data;
return dc;
}
///
/// Creates and starts a new DelayedCall instance with 1 data argument.
///
/// Callback function
/// Argument for callback function
/// Time to callback invocation
/// Newly created DelayedCall instance that can be used for later controlling of the invocation process
public static DelayedCall Start(Callback cb, T data, int milliseconds)
{
DelayedCall dc = Create(cb, data, milliseconds);
dc.Start();
return dc;
}
///
/// Creates and starts a new asynchronous DelayedCall instance with 1 data argument. The callback function will be invoked on a ThreadPool thread.
///
/// Callback function
/// Argument for callback function
/// Time to callback invocation
/// Newly created DelayedCall instance that can be used for later controlling of the invocation process
public static DelayedCall StartAsync(Callback cb, T data, int milliseconds)
{
DelayedCall dc = CreateAsync(cb, data, milliseconds);
dc.Start();
return dc;
}
protected override void OnFire()
{
context.Post(delegate
{
lock (timerLock)
{
// Only fire the callback if the timer wasn't cancelled in this very moment
if (cancelled) return;
}
if (callback != null) callback(data);
}, null);
}
///
/// Resets the timeout and callback function argument to a new value and restarts the timer.
///
/// New callback function argument
/// New timeout value
public void Reset(T data, int milliseconds)
{
lock (timerLock)
{
Cancel();
this.data = data;
Milliseconds = milliseconds;
Start();
}
}
}
///
/// Implements a timer that invokes a method with 2 arguments after a user-defined timeout.
///
public class DelayedCall : DelayedCall
{
public new delegate void Callback(T1 data1, T2 data2);
private Callback callback;
private T1 data1;
private T2 data2;
///
/// Creates a new DelayedCall instance with 2 data arguments.
///
/// Callback function
/// First argument for callback function
/// Second argument for callback function
/// Time to callback invocation
/// Newly created DelayedCall instance that can be used for later controlling of the invocation process
public static DelayedCall Create(Callback cb, T1 data1, T2 data2, int milliseconds)
{
DelayedCall dc = new DelayedCall();
PrepareDCObject(dc, milliseconds, false);
dc.callback = cb;
dc.data1 = data1;
dc.data2 = data2;
return dc;
}
///
/// Creates a new asynchronous DelayedCall instance with 2 data arguments. The callback function will be invoked on a ThreadPool thread.
///
/// Callback function
/// First argument for callback function
/// Second argument for callback function
/// Time to callback invocation
/// Newly created DelayedCall instance that can be used for later controlling of the invocation process
public static DelayedCall CreateAsync(Callback cb, T1 data1, T2 data2, int milliseconds)
{
DelayedCall dc = new DelayedCall();
PrepareDCObject(dc, milliseconds, true);
dc.callback = cb;
dc.data1 = data1;
dc.data2 = data2;
return dc;
}
///
/// Creates and starts a new DelayedCall instance with 2 data arguments.
///
/// Callback function
/// First argument for callback function
/// Second argument for callback function
/// Time to callback invocation
/// Newly created DelayedCall instance that can be used for later controlling of the invocation process
public static DelayedCall Start(Callback cb, T1 data1, T2 data2, int milliseconds)
{
DelayedCall dc = Create(cb, data1, data2, milliseconds);
dc.Start();
return dc;
}
///
/// Creates and starts a new asynchronous DelayedCall instance with 2 data arguments. The callback function will be invoked on a ThreadPool thread.
///
/// Callback function
/// First argument for callback function
/// Second argument for callback function
/// Time to callback invocation
/// Newly created DelayedCall instance that can be used for later controlling of the invocation process
public static DelayedCall StartAsync(Callback cb, T1 data1, T2 data2, int milliseconds)
{
DelayedCall dc = CreateAsync(cb, data1, data2, milliseconds);
dc.Start();
return dc;
}
protected override void OnFire()
{
context.Post(delegate
{
lock (timerLock)
{
// Only fire the callback if the timer wasn't cancelled in this very moment
if (cancelled) return;
}
if (callback != null) callback(data1, data2);
}, null);
}
///
/// Resets the timeout and callback function argument to a new value and restarts the timer.
///
/// First new callback function argument
/// Second new callback function argument
/// New timeout value
public void Reset(T1 data1, T2 data2, int milliseconds)
{
lock (timerLock)
{
Cancel();
this.data1 = data1;
this.data2 = data2;
Milliseconds = milliseconds;
Start();
}
}
}
///
/// Implements a timer that invokes a method with 3 arguments after a user-defined timeout.
///
public class DelayedCall : DelayedCall
{
public new delegate void Callback(T1 data1, T2 data2, T3 data3);
private Callback callback;
private T1 data1;
private T2 data2;
private T3 data3;
///
/// Creates a new DelayedCall instance with 3 data arguments.
///
/// Callback function
/// First argument for callback function
/// Second argument for callback function
/// Third argument for callback function
/// Time to callback invocation
/// Newly created DelayedCall instance that can be used for later controlling of the invocation process
public static DelayedCall Create(Callback cb, T1 data1, T2 data2, T3 data3, int milliseconds)
{
DelayedCall dc = new DelayedCall();
PrepareDCObject(dc, milliseconds, false);
dc.callback = cb;
dc.data1 = data1;
dc.data2 = data2;
dc.data3 = data3;
return dc;
}
///
/// Creates a new asynchronous DelayedCall instance with 3 data arguments. The callback function will be invoked on a ThreadPool thread.
///
/// Callback function
/// First argument for callback function
/// Second argument for callback function
/// Third argument for callback function
/// Time to callback invocation
/// Newly created DelayedCall instance that can be used for later controlling of the invocation process
public static DelayedCall CreateAsync(Callback cb, T1 data1, T2 data2, T3 data3, int milliseconds)
{
DelayedCall dc = new DelayedCall();
PrepareDCObject(dc, milliseconds, true);
dc.callback = cb;
dc.data1 = data1;
dc.data2 = data2;
dc.data3 = data3;
return dc;
}
///
/// Creates and starts a new DelayedCall instance with 3 data arguments.
///
/// Callback function
/// First argument for callback function
/// Second argument for callback function
/// Third argument for callback function
/// Time to callback invocation
/// Newly created DelayedCall instance that can be used for later controlling of the invocation process
public static DelayedCall Start(Callback cb, T1 data1, T2 data2, T3 data3, int milliseconds)
{
DelayedCall dc = Create(cb, data1, data2, data3, milliseconds);
dc.Start();
return dc;
}
///
/// Creates and starts a new asynchronous DelayedCall instance with 3 data arguments. The callback function will be invoked on a ThreadPool thread.
///
/// Callback function
/// First argument for callback function
/// Second argument for callback function
/// Third argument for callback function
/// Time to callback invocation
/// Newly created DelayedCall instance that can be used for later controlling of the invocation process
public static DelayedCall StartAsync(Callback cb, T1 data1, T2 data2, T3 data3, int milliseconds)
{
DelayedCall dc = CreateAsync(cb, data1, data2, data3, milliseconds);
dc.Start();
return dc;
}
protected override void OnFire()
{
context.Post(delegate
{
lock (timerLock)
{
// Only fire the callback if the timer wasn't cancelled in this very moment
if (cancelled) return;
}
if (callback != null) callback(data1, data2, data3);
}, null);
}
///
/// Resets the timeout and callback function argument to a new value and restarts the timer.
///
/// First new callback function argument
/// Second new callback function argument
/// Third new callback function argument
/// New timeout value
public void Reset(T1 data1, T2 data2, T3 data3, int milliseconds)
{
lock (timerLock)
{
Cancel();
this.data1 = data1;
this.data2 = data2;
this.data3 = data3;
Milliseconds = milliseconds;
Start();
}
}
}
}