Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling Method only once from a Threading.Timer

I have a System.Threading.Timer which fires frequently (let's say every second for simplicity), in the CallBack I need to call an Action (which is passed in via the constructor, so sits in another class) within which I do some processing (let's say it takes 2+ seconds), how would I prevent my processing logic from being called multiple times? It seems that a lock() doesn't work within the Action call? I am using .net 3.5.

public TestOperation(Action callBackMethod)
{
    this.timer = new System.Threading.Timer(timer_Elapsed, callbackMethod, timerInterval, Timeout.Infinite);
}

private void timer_Elapsed(object state)
{
    Action callback = (Action) state;
    if (callback != null)
    {
        callback();
    }
}

// example of the callback, in another class. 
private void callBackMethod()
{
    // How can I stop this from running every 1 second? Lock() doesn't seem to work here
    Thread.Sleep(2000);
}

Thanks!

like image 241
Richard Avatar asked Jul 23 '26 15:07

Richard


2 Answers

There's nothing pretty about having to solve this problem. Note that using lock is a very bad idea, it will make your threadpool explode when the callback consistently takes too much time. This happens easily when the machine gets loaded. Using Monitor.TryEnter() is the safe alternative. Definitely not pretty either, you'll arbitrarily lose callbacks.

It gets a heckofalot easier if you simply set the period argument to 0. So that the timer can tick only once. Now you automatically have a hard guarantee that the callback cannot be re-entered. All you have to do is call Change() at the end of the method to restart the timer. It is up to you to use a fixed value or calculate a new dueTime value based on the actual amount of time that expired, either are reasonable choices.

like image 150
Hans Passant Avatar answered Jul 25 '26 04:07

Hans Passant


You could do something like this and avoid timers altogether.

void Main()
{
    RunPeriodicAsync();
}
async Task RunPeriodicAsync()
{
    while(true)
    {
        await Task.Delay(someTimeSpan);
        DoTheThing();
        if(!goAgain)break;
    }

}

or if you need to support cancellation:

void Main()
{
    var cts=new CancellationTokenSource();
    RunPeriodicAsync(cts.Token);
    //sometime later
    cts.Cancel();
}
async Task RunPeriodicAsync(CancellationToken ct)
{
    while(!ct.IsCancellationRequested)
    {
        await Task.Delay(1000);
        DoTheWork();
    }
}

Without async/await you could:

System.Threading.Timer timer;
void Main()
{
    RunActionAfter(() => DoTheWork(), 2000);
}
void RunActionAfter(Action action, int period)
{
    //Timeout.Infinite means the timer runs only once.
    timer = new Timer(_ => action(), null, 2000, Timeout.Infinite); 
}
void DoTheWork()
{
    Console.WriteLine("!!!");

    //then maybe
    RunActionAfter(() => DoTheWork(),2000);
}
like image 23
spender Avatar answered Jul 25 '26 03:07

spender



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!