views:

944

answers:

11

I'm writing a windows service that once started will run every X hours. The process it completes is fairly intensive, so I want to use a background worker. I'm using a Settings file to store both the hours between runs and the last time the service ran.

I'm not exactly sure the best way to do this - that is, I want the service to idle using as few resources as possible, and when it runs, it needs to run in the background worker, report what it did, and then go back into idle mode.

I've thought about using 2 background workers. The first worker would be a private local variable for the service that runs something like this:

while (true)
{
      //create new background worker and run

      Thread.Sleep(Settings.Default.SleepTimeHours * 3600000);
}

with a sub worker that is created each iteration of the loop, and destroyed when completed. To support cancellation, I think I would have to have a local instance of the second worker available in the service, but it will be null if the process currently is not running. When the secondary worker completes, it would send out my reporting, set the last run time in the settings file, and then dispose of the worker and set the reference to null.

I'm wondering if there is a better way to do this or a best practice.

Thanks

+1  A: 

In my company, we rely on windows task scheduler to initiate the service, and then have the service shut itself down. This insures our service is always run at the right time and the scheduling system will allow reporting of success / failure etc.

JustLoren
Wont scale up as nicely as something like Quartz..
David Kiff
Not sure what you mean by scaling up. We have about 15 services that run nightly, and about 30 that run on the weekend. How many services do you think we'll need to run? lol
JustLoren
+1  A: 

Use a timer to do this.

RichardOD
+4  A: 

This is not a very good idea, since you lock your thread for the full period of "SleepTimeHours" and you won't be able to even stop the service in the meantime.

You could either make this service so that it would sleep for e.g. 5 seconds and then check whether it's time to get back to work, and if not, sleep another 5 seconds (that would give you the necessary responsiveness, if you need to stop the service).

OR: you might be better off just writing a console app that can be scheduled using the Windows "scheduled tasks" feature in order to be run every x hours. That way, you won't be blocking or using any system resource if your app isn't doing anything......

Marc

marc_s
Good point on not sleeping the full X hours. Thanks, Marc.
Josh
Rather than while(true) you could add a while (_isPolling) then when you stop the service it will stop and the thread will finish up when it eventually awakes :)
David Kiff
I was about to suggest the scheduled task solution instead of a service.
Philippe
I agree with this, scheduled tasks can be just as useful as a service, and you can even check a box that says "Don't start a new instance if one is already running" (in case the time to launch again happens when you're still processing the last batch). If your service is going to be idle most of the time, do you really need to have resources wasted on nothing? Each process running is a process vulnerable to attack. Thankfully Google moved from a service updater to a scheduled task updater, since the service didn't do anything about 99%(arbitrary number) of the time.
Joshua
+2  A: 

Consider a job scheduler like Quartz.Net.

http://quartznet.sourceforge.net/

Austin Salonen
+2  A: 

You don't really want to use background worker. Background workers are used when you have something going one at the foreground (such as UI) and you also want something else to be done on the background. In your case there's no foreground processing, all you have is a single job that you need to run every once in a while.

Also, don't bother with the sleeping business etc. Instead, use a scheduler framework, such as Quartz.NET to wake it up every once in a while. This way when your service starts it will initialize the scheduler and do absolutely nothing until scheduler wakes up. When scheduler wakes up it will call a method of an object that you tell it to call when you initialize it.

This way the service will barely consume any CPU when idle and work full steam when needed.

zvolkov
+7  A: 

I typically use a Timer, then stop it when the process starts to run.

Here's an article that explains how to do it.

CitizenBane
+1: good tutorial link
Zenuka
That article was a good link. Thank you.
Josh
However I believe that will still lock up the timer event thread, which could cause the service to stop responding to Start(), Stop(), Reset() etc. Service's should be able to respond to these within 30 seconds, so a threaded non-blocking approach would be better.
Ian
That's not true, the timer doesn't block the current thread...
Zenuka
+1  A: 

How about something more like this:

    public class LongRunningService : ServiceBase
{
    System.Threading.Thread processThread;
    System.Timers.Timer timer;
    private Boolean Cancel;

    protected override void OnStart(string[] args)
    {
        timer = new Timer(Settings.Default.SleepTimeHours * 3600000);
        timer.Elapsed += new ElapsedEventHandler(timer_Tick);
        timer.Start();

        Cancel = false;
    }

    protected override void OnContinue()
    {
        timer.Start();
    }

    protected override void OnPause()
    {
        timer.Stop();
    }

    protected override void OnStop()
    {
        if (processThread.ThreadState == System.Threading.ThreadState.Running)
        {
            Cancel = true;
            // Give thread a chance to stop
            processThread.Join(500);
            processThread.Abort();
        }
    }

    void timer_Tick(object sender, EventArgs e)
    {
        processThread = new System.Threading.Thread(new ThreadStart(DoWork));
        processThread.Start();
    }

    private void DoWork()
    {
        try
        {
            while (!Cancel)
            {

            if (Cancel) { return; }
            // Do General Work

            System.Threading.Thread.BeginCriticalRegion();
            {
                // Do work that should not be aborted in here.
            }
            System.Threading.Thread.EndCriticalRegion();
            }

        }
        catch (System.Threading.ThreadAbortException tae)
        {
            // Clean up correctly to leave program in stable state.
        }
    }
}
Ian
I would suggest using the System.Timers.Timer class instead of the System.Windows.Forms.Timer. The Forms.Timer will require the System.Windows.Forms.dll to be loaded, which is not something that is generally needed for Windows services.
Matt Davis
I agree actually. Updated the example.
Ian
What's the purpose of the while (!Cancel) {} in DoWork() and how will the execution ever go below the if (Cancel) {return;}? Perhaps the end brace of the while should be near the end?
pug
pug, yes you are correct, the end brace of the while should be below the EndCriticalRegion call. Will update to reflect in the example code.
Ian
+1  A: 

Blog.StackOverflow.com has an interesting article on using cache expiration to handle periodic tasks:

http://blog.stackoverflow.com/2008/07/easy-background-tasks-in-aspnet/

Keltex
+1  A: 

I had the same discussion with colleagues around 1hour ago! I went with the while(_isPolling) option, because i needed the work to happen syncronously. I didnt want the same work being picked up by other thread (the Timer approach), and implementing extra locking for that seemed like a waste.

David Kiff
+1  A: 

Use an approach based on the System.Threading.WaitHandle approach.

using System.Threading;
private Thread _thread;
private ManualResetEvent _shutdownEvent = new ManualResetEvent(false);
private ManualResetEvent _scheduleEvent = new ManualResetEvent(false);
private System.Timers.Timer _scheduleTimer = new System.Timers.Timer();
protected override void OnStart(string[] args)
{
    // Configure the timer.
    _scheduleTimer.AutoReset = false;
    _scheduleTimer.Interval = 120000; // 2 minutes in milliseconds
    _scheduleTimer.Elapsed += delegate { _scheduleEvent.Set(); }

    // Create the thread using anonymous method.
    _thread = new Thread( delegate() {
        // Create the WaitHandle array.
        WaitHandler[] handles = new WaitHandle[] {
            _shutdownEvent,
            _scheduleEvent
        };
        // Start the timer.
        _scheduleTimer.Start();
        // Wait for one of the events to occur.
        while (!_shutdownEvent.WaitOne(0)) {
            switch (WaitHandle.WaitAny(handles)) { 
               case 0:  // Shutdown Event
                   break;
               case 1:  // Schedule Event
                   _scheduleTimer.Stop();
                   _scheduleEvent.Reset();
                   ThreadPool.QueueUserWorkItem(PerformScheduledWork, null);
                   break;
               default:
                   _shutdownEvent.Set(); // should never occur
            }
        }
    } );
    _thread.IsBackground = true;
    _thread.Start();
}
protected override void OnStop()
{
    // Signal the thread to shutdown.
    _shutdownEvent.Set();
    // Give the thread 3 seconds to terminate.
    if (!_thread.Join(3000)) {
        _thread.Abort(); // not perferred, but the service is closing anyway
    }
}
private void PerformScheduledWork(object state)
{
    // Perform your work here, but be mindful of the _shutdownEvent in case
    // the service is shutting down.
    //
    // Reschedule the work to be performed.
     _scheduleTimer.Start();
}
Matt Davis
+2  A: 

Someone had a similar question over on Super User. You could install a tool that monitors windows services. Something like Service Hawk would help you keep the services started, or allow you to schedule automatic restarts (possibly during the night) to keep the service running smoothly.

ExtraLean