does the timer thread wait till all the steps in t

2020-06-28 01:24发布

问题:

I have a timer thread function

SampleTimer = new Timer(SomeTask,0,70000)

call back function is as below

void SomeTask(object o)
{
//block using autoresetevent
}

The issue is the SomeTask() callback method gets called every 70 secs even though all the operations in the callback method is still not done. How can I prevent the timer from calling the SomeTask() function before all the steps within it are completed

回答1:

I assume you're using a System.Threading.Timer.

One way to do it is to create the timer as a one-shot, and then restart it after the thread has completed its task. That way you're certain that you won't have any overlap:

myTimer = new Timer(someMethod, null, 70000, Timeout.Infinite);

And in your callback:

void TimerCallback(object o)
{
    // do stuff here
    // then change the timer
    myTimer.Change(70000, Timeout.Infinite);
}

Specifying Timeout.Infinite for the period disables periodic signaling, turning the timer into a one-shot.

Another way is to use a monitor:

object TimerLock = new object();

void TimerCallback(object o)
{
    if (!Monitor.TryEnter(TimerLock))
    {
        // already in timer. Exit.
        return;
    }

    // do stuff

    // then release the lock
    Monitor.Exit(TimerLock);
}

If you're wondering why I don't use a try/finally for the lock, see Eric Lippert's blog, Locks and exceptions do not mix.

The primary difference in these two approaches is that in the first the timer will fire 70 seconds after the previous callback execution finishes. In the second the timer will fire on a 70 second period, so the next callback might execute any time after the previous one finishes, from one second later to 70 seconds later.

For most things I've done, the first technique I showed seems to work better.



回答2:

Concurrent invocations are possible which is a very annoying property of .NET and Windows timers. You have to ensure mutual exclusion yourself, maybe just using a lock (no events necessary). That gives you another problem though because if the timer ticks too fast an arbitrary number of threads might queue up in front of the lock. So it is better to TryEnter the lock and do nothing if it cannot be entered. You loose one tick that way but as ticks are not guaranteed anyway your application must be able to cope with that.

When working with timers very little is ever guaranteed about the time, count and concurrency of ticks.



回答3:

I believe the object o is the timer being fired. So you may be able to do:

var myTimer = object as Timer;

(but I would check on that...)

It will fire on your interval no matter how long your SomeTask takes.

I always do something like this

private System.Timers.Timer _timer;

public static void main()
{
    _timer = SampleTimer = new Timer(SomeTask,0,70000);
}

void SomeTask(object o)
{
    _timer.Enabled = false;
    try{
    // ...  work...
    }finally{
        _timer.Enabled = true;
    }
}

UPDATE: a bit safer with a lock.

private static object _padLock = new object();

void SomeTask(object o)
{
    // lock around disabling the timer
    lock(_padLock)
    {
        // return if some other thread beat us to it.
        if (!_timer.Enabled) return;
        _timer.Enabled = false;
    }

    try{
    // ...  work...
    }finally{
        _timer.Enabled = true;
    }
}

To be super duper safe. And if you only ever want one instance of "SomeTask" running. AND, you don't want them to queue up. You should put a lock() in SomeTask. And you can skip disabling the timer.