Domanda

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!

È stato utile?

Soluzione

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.

Altri suggerimenti

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);
}

you can use a boolean flag to prevent reentrance:

    bool executing;

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

    private void timer_Elapsed(object state)
    {
        if(executing)
             return;


        Action callback = (Action) state;
        if (callback != null)
        {
            executing = true;
            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);
        executing = false;

    }
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top