Make a .net System.Timers.Timer fire immediately
I am using a Timer to run an event periodically on a reasonably long interval (2 minutes). This is working fine. However I would like the event to fire immediately when the timer is created (instead of waiting 2 minutes).
Note that I can't do this just by calling the method, since it takes some time to run and would block the application. I need the timer to fire as normal and run the event in a separate thread.
The best way I can think of doing this at the moment is subclassing the timer and creating a TriggerManually
method that would do something like this:
- Turn auto reset off
- Set the interval to 1ms
- Enable the tim开发者_如何学Goer
This would trigger the elapsed event straight away, and I could put all the settings back to normal.
Seems a bit roundabout though. Is there a better way to do it?
Couldn't you just call your event handler for the elapsed event manually?
Even if you were expecting it to execute on a thread pool thread, you could invoke it.
class Blah
{
private Timer mTimer;
public Blah()
{
mTimer = new Timer(120000);
ElapsedEventHandler handler = new ElapsedEventHandler(Timer_Elapsed);
mTimer.Elapsed += handler;
mTimer.Enabled = true;
//Manually execute the event handler on a threadpool thread.
handler.BeginInvoke(this, null, new AsyncCallback(Timer_ElapsedCallback), handler);
}
private static void Timer_Elapsed(object source, ElapsedEventArgs e)
{
//Do stuff...
}
private void Timer_ElapsedCallback(IAsyncResult result)
{
ElapsedEventHandler handler = result.AsyncState as ElapsedEventHandler;
if (handler != null)
{
handler.EndInvoke(result);
}
}
}
I liked Rob Cooke's answer, so I built a small EagerTimer
class that subclasses System.Timers.Timer
and adds this functionality. (With hints from these articles)
I know I could use System.Threading.Timer
instead, but this is simple and works well in my application.
EagerTimer
/// <summary>
// EagerTimer is a simple wrapper around System.Timers.Timer that
// provides "set up and immediately execute" functionality by adding a
// new AutoStart property, and also provides the ability to manually
// raise the Elapsed event with RaiseElapsed.
/// </summary>
public class EagerTimer : Timer
{
public EagerTimer()
: base() { }
public EagerTimer(double interval)
: base(interval) { }
// Need to hide this so we can use Elapsed.Invoke below
// (otherwise the compiler complains)
private event ElapsedEventHandler _elapsedHandler;
public new event ElapsedEventHandler Elapsed
{
add { _elapsedHandler += value; base.Elapsed += value; }
remove { _elapsedHandler -= value; base.Elapsed -= value; }
}
public new void Start()
{
// If AutoStart is enabled, we need to invoke the timer event manually
if (AutoStart)
{
this._elapsedHandler.BeginInvoke(this, null, new AsyncCallback(AutoStartCallback), _elapsedHandler); // fire immediately
}
// Proceed as normal
base.Start();
}
private void AutoStartCallback(IAsyncResult result)
{
ElapsedEventHandler handler = result.AsyncState as ElapsedEventHandler;
if (handler != null) handler.EndInvoke(result);
}
// Summary:
// Gets or sets a value indicating whether the EagerTimer should raise
// the System.Timers.Timer.Elapsed event immediately when Start() is called,
// or only after the first time it elapses. If AutoStart is false, EagerTimer behaves
// identically to System.Timers.Timer.
//
// Returns:
// true if the EagerTimer should raise the System.Timers.Timer.Elapsed
// event immediately when Start() is called; false if it should raise the System.Timers.Timer.Elapsed
// event only after the first time the interval elapses. The default is true.
[Category("Behavior")]
[DefaultValue(true)]
[TimersDescription("TimerAutoStart")]
public bool AutoStart { get; set; }
/// <summary>
/// Manually raises the Elapsed event of the System.Timers.Timer.
/// </summary>
public void RaiseElapsed()
{
if (_elapsedHandler != null)
_elapsedHandler(this, null);
}
}
Unit Tests
[TestClass]
public class Objects_EagerTimer_Tests
{
private const int TimerInterval = 10; // ms
private List<DateTime> _timerFires = new List<DateTime>();
private DateTime _testStart;
[TestInitialize]
public void TestSetup()
{
_timerFires.Clear();
_testStart = DateTime.Now;
}
[TestMethod]
public void Objects_EagerTimer_WithAutoStartDisabled()
{
// EagerTimer should behave as a normal System.Timers.Timer object
var timer = new EagerTimer(TimerInterval);
timer.AutoReset = false;
timer.Elapsed += timerElapsed;
timer.Start();
// Wait (not enough time for first interval)
Thread.Sleep(5);
Assert.IsFalse(_timerFires.Any());
// Wait a little longer
Thread.Sleep(TimerInterval);
Assert.AreEqual(1, _timerFires.Count);
}
[TestMethod]
public void Objects_EagerTimer_WithAutoStartEnabled()
{
// EagerTimer should fire immediately on Start()
var timer = new EagerTimer(TimerInterval);
timer.AutoReset = false;
timer.AutoStart = true;
timer.Elapsed += timerElapsed;
timer.Start();
// Wait (not enough time for first interval)
Thread.Sleep(5);
Assert.IsTrue(_timerFires.Any());
// Wait a little longer, now it will have fired twice
Thread.Sleep(TimerInterval);
Assert.AreEqual(2, _timerFires.Count);
}
[TestMethod]
public void Objects_EagerTimer_WhenRaisingManually()
{
// EagerTimer should fire immediately on Start()
var timer = new EagerTimer(TimerInterval);
timer.AutoReset = false;
timer.AutoStart = false;
timer.Elapsed += timerElapsed;
Assert.IsFalse(_timerFires.Any());
timer.RaiseElapsed();
Assert.IsTrue(_timerFires.Any());
}
private void timerElapsed(object sender, ElapsedEventArgs e) {
_timerFires.Add(DateTime.Now);
}
}
Could you use a System.Threading.Timer instead ? It has a constructor that lets you choose the interval as well as the delay (which can be set to 0 to begin immediately). http://msdn.microsoft.com/en-us/library/2x96zfy7.aspx
精彩评论