views:

265

answers:

6

I am aware that System.Threading.Timer exists, but I already have a Thread. This thread is supposed to stay alive all the time, but only execute every X seconds. The test implementation looks like this:

public class MailClass
{
    private Action<string> LoggerAction;
    private bool _exit;

    public MailClass(Action<string> loggerAction)
    {
        LoggerAction = loggerAction;
    }

    public void Run()
    {
        LoggerAction("Run called");
        _exit = false;
        while(!_exit)
        {
            Thread.Sleep(TimeSpan.FromSeconds(300));
            LoggerAction("Waking up");
        }
        LoggerAction("Run ended");
    }

    public void Stop()
    {
        LoggerAction("Stop called");
        _exit = true;
    }
}

The Run method executes, then sleeps for 5 Minutes, then executes again. So it's basically a timer that fires every 5 Minutes + the time it takes to execute the action. (and yes, I should cache the TimeSpan instead of re-creating it over and over)

Is this the proper way to do it? (In the real app, the Run action checks a Web Service, so I have no way to signal my Thread to wake up earlier)

Or should I use some other concept to have the thread? One problem I see is the implementation of Stop. The Run Thread runs a loop that checks a bool every time, but if I call Stop() I have to wait until the Sleep Interval is over, which is inconvenient.

Thread.Abort would be harsh, so I guess Thread.Interrupt would work somehow? The Stop() Method should allow Run to finish it's current iteration, so no hard abort. AutoResetEvent looks a bit like what I could need, but I don't fully understand what it does.

Edit: One way I would see this possible is to add a Timer (so a separate thread) and then have Run() end not with Thread.Sleep but with some "Wait until some object changes". I would then change that object either from the second Thread (when the 5 minutes expire) or from the Stop action. But that seems excessive? Essentially, Run needs to react to two conditions: 5 Minutes expire or some external signal (like the change of the _exit flag). Something tells me there should be something built-in, but maybe having another Timer Thread solely focused on sending a signal every 5 minutes is the way to go?

A: 

Seems like a good solution to me. If you're worried about stopping sooner, you can set the sleep time to be less and keep a count so you only run the actual code every 5 minutes. That way it's checking the boolean more often and can break out sooner.

Myles
That sounds rather hacky though to be honest :/ it"s not as bad as creating a SpinLock, but something tells me that there has to be a better implementation.
Michael Stum
+1  A: 

yeah that's cool, you can also call Thread.Interrupt() to interrupt the sleep, rather than waiting for sleep to return normally.

in the case the thread is not blocking when you interrupt it, it will continue processing normally until it tries to sleep again.

jspcal
+4  A: 

If you're forced to poll, then you're forced to poll. Thread.Sleep() is fine for that.

However with regards to you're interrupt concerns...

I'd re-write your solution a bit to use Monitor.Wait/Pulse. That does require you keep an object around solely to lock(...){} on it, but it strikes me as a cleaner solution.

I say cleaner because using Thread.Interrupt() is effectively using exceptions for "normal" control flow. Stopping a Timer is in no way unexpected. But its a design smell really (if such things exist), nothing more.

Quicky outline:

//Instead of Thread.Sleep(FIVE_MIN) in Run()...
lock(some_obj)
{
  if(Monitor.Wait(some_obj, FIVE_MIN))  //Wait for 5 min (or whatever) or until some_obj is Pulse'd
  {
    //Got Pulse
  }
  else
  {
    //Timeout expired
  }
}

//And in Stop()...
_exit = true;
lock(some_obj)
{
  Monitor.Pulse(some_obj);  //Wakeup the thread in Run() if it's currently Wait'ing
}
Kevin Montrose
How would that roughly work? I basically need to change Run() to do something EITHER if some external "Signal" comes in _or_ until the 5 Minutes expire. See my edit.
Michael Stum
See edit. Hopefully that's clear enough.
Kevin Montrose
Awesome, that worked! The Documentation is really unintuitive, but that example was helpful :)
Michael Stum
This looks OK, but be aware that when a Pulse() happens when nobody is parked in Wait() then the Pulse is lost. This can be tricky.
Henk Holterman
How is using Thread.Interrupt like using exceptions? It's a convenient way of waking up threads, and it is only allowed when you're doing an alertable wait. It also handles the case where the thread is alerted (that's the proper term) before the wait occurs.
wj32
... its like using exceptions because Thread.Interrupt() causes an exception.
Kevin Montrose
A: 

You could look into System.Timers.Timer as well, though truthfully just sleeping is not a bad solution.

anq
A: 

Is there a reason you couldn't just use a timer inside the thread? You'd get what you want, a thread that stays alive forever while firing off your method, plus you could just stop the timer at any point without waiting for 5 minutes or interrupting threads?

(I'm not very experienced in threading, so I might be missing something obvious?)

mpeterson
Not really, I just "feel" that spinning of a secondary thread of a thread is excessive, but I'm not experienced either.
Michael Stum
One drawback to this is that it uses another thread from the ThreadPool.
gWiz
+1  A: 

If time interval is critical then prefer high resolution timers provided in windows which will trigger with higher accuracy.

Kavitesh Singh
Thanks. No, in this case it's unimportant if it takes a few milliseconds (or even seconds) more or less.
Michael Stum