I have a System.Threading.Timer that calls its appropriate event handler (callback) every 10 ms. The method itself is not reentrant and can
You could let the timer continue firing the callback method but wrap your non-reentrant code in a Monitor.TryEnter/Exit. No need to stop/restart the timer in that case; overlapping calls will not acquire the lock and return immediately.
private void CreatorLoop(object state)
{
if (Monitor.TryEnter(lockObject))
{
try
{
// Work here
}
finally
{
Monitor.Exit(lockObject);
}
}
}
I've had similar situation with a System.Timers.Timer, where the elapsed event is executed from a threadpool and needs to be reentrant.
I used this method to get around the issue:
private void tmr_Elapsed(object sender, EventArgs e)
{
tmr.Enabled = false;
// Do Stuff
tmr.Enabled = true;
}
Depending on what you're doing you may want to consider a System.Timers.Timer, here's a nice summary from MSDN
System.Windows.Forms System.Timers System.Threading
Timer event runs on what thread? UI thread UI or worker thread Worker thread
Instances are thread safe? No Yes No
Familiar/intuitive object model? Yes Yes No
Requires Windows Forms? Yes No No
Metronome-quality beat? No Yes* Yes*
Timer event supports state object? No No Yes
Initial timer event can be scheduled? No No Yes
Class supports inheritance? Yes Yes No
* Depending on the availability of system resources (for example, worker threads)
//using Timer with callback on System.Threading namespace
// Timer(TimerCallback callback, object state, int dueTime, int period);
// TimerCallback: delegate to callback on timer lapse
// state: an object containig information for the callback
// dueTime: time delay before callback is invoked; in milliseconds; 0 immediate
// period: interval between invocation of callback; System.Threading.Timeout.Infinity to disable
// EXCEPTIONS:
// ArgumentOutOfRangeException: negative duration or period
// ArgumentNullException: callback parameter is null
public class Program
{
public void Main()
{
var te = new TimerExample(1000, 2000, 2);
}
}
public class TimerExample
{
public TimerExample(int delayTime, int intervalTime, int treshold)
{
this.DelayTime = delayTime;
this.IntervalTime = intervalTime;
this.Treshold = treshold;
this.Timer = new Timer(this.TimerCallbackWorker, new StateInfo(), delayTime, intervalTime);
}
public int DelayTime
{
get;
set;
}
public int IntervalTime
{
get;
set;
}
public Timer Timer
{
get;
set;
}
public StateInfo SI
{
get;
set;
}
public int Treshold
{
get;
private set;
}
public void TimerCallbackWorker(object state)
{
var si = state as StateInfo;
if (si == null)
{
throw new ArgumentNullException("state");
}
si.ExecutionCounter++;
if (si.ExecutionCounter > this.Treshold)
{
this.Timer.Change(Timeout.Infinite, Timeout.Infinite);
Console.WriteLine("-Timer stop, execution reached treshold {0}", this.Treshold);
}
else
{
Console.WriteLine("{0} lapse, Time {1}", si.ExecutionCounter, si.ToString());
}
}
public class StateInfo
{
public int ExecutionCounter
{
get;
set;
}
public DateTime LastRun
{
get
{
return DateTime.Now;
}
}
public override string ToString()
{
return this.LastRun.ToString();
}
}
}
// Result:
//
// 1 lapse, Time 2015-02-13 01:28:39 AM
// 2 lapse, Time 2015-02-13 01:28:41 AM
// -Timer stop, execution reached treshold 2
//
A couple possible solutions:
You may be able to manage option #2 without disposing/creating a new object by using the Change()
method of the original timer object, but I'm not sure what the behavior is exactly of calling Change()
with a new start timeout after the first timeout has expired. That would be worth a test or two.
Edit:
I did the test - manipulating the timer as a restartable one-shot seems to work perfectly, and it's much simpler than the other methods. Here's some sample code based on yours as a starting point (a few details may have changed to get it to compile on my machine):
private Timer _creatorTimer;
// BackgroundWorker's work
private void CreatorWork(object sender, EventArgs e) {
// note: there's only a start timeout, and no repeat timeout
// so this will fire only once
_creatorTimer = new Timer(CreatorLoop, null, 1000, Timeout.Infinite);
// some other code that worker is doing while the timer is active
// ...
// ...
}
private void CreatorLoop(object state) {
Console.WriteLine( "In CreatorLoop...");
/*
... Work here
*/
Thread.Sleep( 3000);
// Reenable timer
Console.WriteLine( "Exiting...");
// now we reset the timer's start time, so it'll fire again
// there's no chance of reentrancy, except for actually
// exiting the method (and there's no danger even if that
// happens because it's safe at this point).
_creatorTimer.Change(1000, Timeout.Infinite);
}
I do it with Interlocked that provides atomic operations, and by CompareExchange ensures that only one thread at a time enters the critical section:
private int syncPoint = 0;
private void Loop()
{
int sync = Interlocked.CompareExchange(ref syncPoint, 1, 0);
//ensures that only one timer set the syncPoint to 1 from 0
if (sync == 0)
{
try
{
...
}
catch (Exception pE)
{
...
}
syncPoint = 0;
}
}