8

I'm working on a Windows Service that's having some issues with Thread.Sleep() so I figured I would try to use a timer instead as this question recommends:

Using Thread.Sleep() in a Windows Service

Thing is it's not entirely clear to me how one might implement this. I believe this is the way but I just wanted to make sure:

'' Inside The Service Object
Dim closingGate As System.Threading.AutoResetEvent

Protected Overrides Sub OnStart(ByVal args() As String)
   Dim worker As New Threading.Thread(AddressOf Work)
   worker.Start()
End Sub

Protected Sub Work()
   Dim Program = New MyProgram()
   closingGate = New System.Threading.AutoResetEvent(False)
   Program.RunService(closingGate)
End Sub

Protected Overrides Sub OnStop()
   closingGate.Set()
End Sub

'' Inside My Programs Code:
Public Sub RunService(closingGate As AutoResetEvent)
   Dim tmr As New Timer
   '' ...and so on

   closingGate.WaitOne()
End Sub

Aside from using VB.Net (Kidding, but I'd be using C# if I could.) am I on the right track here? Is this better than using Thread.Sleep()?

Community
  • 1
  • 1
Spencer Ruport
  • 34,865
  • 12
  • 85
  • 147

2 Answers2

23

Honestly, I would have to say that I think you're a little off the beaten path here.

First of all, the actual code posted doesn't really make sense; the worker thread is waiting for a signal but for no reason - it's not actually in a loop of any kind or waiting for a resource. Second, if you did need to execute some (perhaps omitted) cleanup code in the worker after receiving the shutdown signal, your service might not give the worker thread enough time to clean up.

But, more fundamentally, all you've done is move the problem to a separate thread. This may keep the service responsive to the service controller but it doesn't fix the design problem - and it adds a lot of unnecessary complexity by using the threads and mutexes; ultimately it's just going to make your service harder to debug if you ever need to.

Let's say you need to execute some code every 5 seconds. The idea is, instead of "waiting" 5 seconds, you invert the control, and let the timer invoke your work.

This is bad:

protected override void OnStart(string[] args)
{
    while (true)
    {
        RunScheduledTasks();    // This is your "work"
        Thread.Sleep(5000);
    }
}

This is good:

public class MyService : ServiceBase
{
    private Timer workTimer;    // System.Threading.Timer

    protected override void OnStart(string[] args)
    {
        workTimer = new Timer(new TimerCallback(DoWork), null, 5000, 5000);
        base.OnStart(args);
    }

    protected override void OnStop()
    {
        workTimer.Dispose();
        base.OnStop();
    }

    private void DoWork(object state)
    {
        RunScheduledTasks();  // Do some work
    }
}

That's it. That's all you have to do to do work at regular intervals. As long as the work itself doesn't run for seconds at a time, your service will remain responsive to the service controller. You can even support pausing under this scenario:

protected override void OnPause()
{
    workTimer.Change(Timeout.Infinite, Timeout.Infinite);
    base.OnPause();
}

protected override void OnContinue()
{
    workTimer.Change(0, 5000);
    base.OnContinue();
}

The only time when you need to start creating worker threads in your service is when the work itself can run for a very long time and you need the ability to cancel in the middle. That tends to involve a good deal of design effort, so I won't get into that without knowing for sure that it's related to your scenario.

It's best not to start introducing multithreaded semantics into your service unless you really need it. If you're just trying to schedule small-ish units of work to be done, you definitely don't need it.

Aaronaught
  • 120,909
  • 25
  • 266
  • 342
  • For some reason I was under the impression that if I didn't have a thread running the service would terminate. – Spencer Ruport Mar 09 '10 at 01:52
  • @Spencer Ruport: There is still a thread running when the `OnStart` method finishes, same as there's still a thread running when `OnLoad` finishes in a WinForms `Form`. After running your `OnStart` code, the service goes into a message loop where it waits for signals from the service controller. – Aaronaught Mar 09 '10 at 02:27
  • What if RunScheduledTasks() takes an unpredictable and potentially long time to run? In the context of the above example, let's say it might take up to 15 seconds to run. In that case the timer approach would no longer work, would it? I mean the idea would be to have 5 seconds between each call to RunScheduledTasks(). In that case the Thread.Sleep approach seems best suited for job, unless I am missing something? – Florin Dumitrescu Aug 11 '11 at 17:17
  • @Florin: If you want the delay to be between the *end* of the first task and the *beginning* of the next, then you can set the timer to only fire once, and then reset it after the task completes. That will have exactly the same timing as the `Thread.Sleep` approach but won't block the service control. Realistically, if the timing changes so drastically, you should probably be using something more robust anyway, i.e. a job scheduler and/or message queue. – Aaronaught Aug 11 '11 at 23:13
  • @Aaronaught: If creating a separate thread on the OnStart method and implementing all logic on it, Theread.Sleep will not block the service. I am not sure a scheduler will work, for the same reason the original timer approach won't. The second one, with timer firing only once and resetting after each task completion would work, but honestly I find it less elegant than the Thread.Sleep. Are there any reasons why Thread.Sleep should be avoided for this particular case, except for the service block? – Florin Dumitrescu Aug 12 '11 at 03:20
  • @Florin: Using `Thread.Sleep` is "elegant" in the same way as `Application.DoEvents` is... i.e. it's a hack. What you're really modelling is an event, not a loop, and you're wasting a thread in the process. You also get no guarantees about the actual timing; `Sleep(x)` actually means "sleep for *at least* x" - if other threads are busy then it might be a lot longer. Can you get away with this in a small service? Sure, just like you can get away with a lot of other bad designs in very small projects. Just know that it's not a good practice. – Aaronaught Aug 12 '11 at 03:47
  • P.S. Most schedulers handle this scenario just fine. Quartz uses the concept of "misfires", and the one I use can run in either queued or most-recent mode, depending on whether or not each instance actually needs to run. Concurrency and conflicts are one of the main problems that a scheduling engine is *intended* to solve. – Aaronaught Aug 12 '11 at 03:51
  • @Aaronaught: thanks for your thoughts, I agree with what you are saying about Thread.Sleep, though I believe you are a bit too categorical. Parking what might end up in a long argument aside, what is the scheduler that you are using? Does it support queued execution with a pause between each job? Because that's what I believe most developers that use Thread Sleep inside a loop want to achieve. – Florin Dumitrescu Aug 12 '11 at 09:34
  • @Florin: I use the one I wrote, it doesn't have an official release yet. What it supports is stable start times; for example, if you schedule a job to run every hour at the 15-minute mark, and the job started at 1:15 doesn't stop until 6:00, then the next event moves to either 5:15 or 6:15 depending on configuration. The 3rd option is to queue up *all* missed instances in case each represents a unique unit of work, but that is not normally the case with scheduled jobs. I personally don't see the point of "unstable start times, stable delay", but I'll listen to use cases if you can name some. – Aaronaught Aug 12 '11 at 21:16
  • I have a question, after the timer is set, how is the timer executed multiple times? I understand with the while(true) but after onstart executes is like some kind of loop? – kprincipe May 27 '19 at 15:00
1

I recently wrote a service that had to do some work, then wait n minutes, then loop.

I also used a ManualResetEvent (called _evt), and a call to Set() in OnStop. But I didn't need a timer.

In the service thread, I had:

for (;;) {

   // do some work
   if (_evt.WaitOne(_waitInterval)) {

       // got the signal => time to leave
       break;
   }
}

So, either a timeout occurred => time for some more work. Or Set() is called and the loop is terminated.

Timores
  • 14,439
  • 3
  • 46
  • 46
  • Interesting. How much time was the work taking? Would there be times when hitting Stop in Windows would hang while the code is getting back to `WaitOne(n)`? – Spencer Ruport Mar 09 '10 at 00:22
  • The work could take a couple of seconds. The 1st thing I do in OnStop is to tell the SCM to please wait 10 seconds. – Timores Mar 09 '10 at 07:07