views:

165

answers:

4

I am using a System.Windows.Forms.Timer component on an application's main form which checks every minute for updates on a database. However, there are a number of possible interactions with the user, such as modal dialogs being thrown for certain operations. In general, I don't want this timer to fire when the user is "busy" or while another database operation is active.

My current approach is to explicitly bracket such "sensitive" blocks of code with a checkDatabaseTimer.Stop()/Start() pair (using try/finally to ensure the Start is always called on the way out).

But this seems clunky to me: if any coders of the future make amendments to this form's code, they need to remember to apply this Stop/Start bracket around any code that, for example, prompts the user for an input. It would seem easy to forget to do this.

Is there a better way of handling this? Or is it just something we have to be conscious of because multithreading is like running with scissors, not one of those things you can just pretend is easy and safe?

+2  A: 

It's not 100% clear but you might be able to do some thing with application events depending on exactly when you want to block the timer

alternatively you can reduce the code at each call site by making a helper object that implements IDisposable to start/stop the timer in an RAII style, you could then use using blocks with this helper object.

public class Suspend : IDisposable
{ 
    private Timer timerRef;
    public Suspend(Timer timer)
    {   
        timerRef = timer
        timer.Stop();
    }
    public void Dispose()
    {
        timerRef.Start();
        //note we don't actually want to dispose the timer here
    }
}

using(var s = new Suspend(myTimer)
{
    //do stuff
}
jk
+3  A: 

First System.Forms.Timer does not run the action in a different thread - it just posts a message to the form's message loop and the form handle the timer event when it's free.

So if you user is in the middle of an action - the timer will "wait" until the action is over;

To explicitly make the timer "start/stop" I prefer using a boolean value that is checked at the beginning of the method the timer runs.

You can use an helper class to reduce the "coding overhead" on your methods:

public class TimerEnableFlag
{
    public bool IsTimerEnabled{get; set;}
}

public class TimerEnabler : IDisposable
{
   private TimerEnableFlag flag;

   public TimerEnabler(TimerEnableFlag flag)
   {
       this.flag = flag;
       flag.IsTimerEnabled = true;
   }

   public void Dispose()
   {
      flag.IsTimerEnabled = false;
   }
}

Now all you need to do is have a "global variable" that holds the timer enable flag that you check when you enter the method run by the timer and when you wanto to disable the timer just add the following code:

using(var enabler = new TimerEnabler(m_flag))
{
    //...
}
Dror Helper
Ah, now I'm glad I added a component timer - looks like I don't need to worry so much when I'm off doing something on the DB, just when I'm doing something funky with the user. I completely forgot about "using" - nice way to simplify the control block.
Joel Goodwin
slight omission in the code sample: TimerEnabler need to hold a reference to the flag so Dispose() can change it.
jk
#jk - just fixed it, thanks
Dror Helper
+1  A: 

If you want the code in the timer event not to run when a dialog box or other is doing other stuff then you can check for that in the timer. Instead of making ALL other code "timer aware", make the timer aware of blocking rules.

This block the timer if a message box is open or if a form owned by the form the timer is on is open. You could add to it to handle your specifik cases:

public bool BlockTimer()
{
    //check if a form owned by this form is open
    foreach (Form f in Application.OpenForms) {
        if (object.ReferenceEquals(f.Owner, this)) {
            //Another form open, dont run timer code
            return true;
        }
    }
    //check if a messagebox or inputbox is open
    if (this.ActiveForm == null) {
        //Message box is active, dont run timer code
        return true;
    }


    return false;
}

In the timer event, just call the function before any other code:

{
    if (BlockTimer() == true) return;

}
Stefan
This is another interesting idea. I don't think I'll go for this, as it might sweep the timer under the rug so far we might forget about it making an update to BlockTimer. But this is nice.
Joel Goodwin
A: 

How about using a separate System.Timers.Timer? System.Timers.Timer creates/ uses Thread pool in elapsed event. Besides it will accurately fire after defined interval unlike System.Windows.Forms.Timer.

PRR
I'm not bothered about exact timing, I just want to block the event in the most straightforward way. As indicated by Dror, the Timer component will not fire if the application is busy - only if messages are being processed, which means I have less scenarios to worry about.
Joel Goodwin