Windows service OnStop wait for finished processing

前端 未结 2 1671
伪装坚强ぢ
伪装坚强ぢ 2020-12-31 05:53

I actually develop a Windows service in VS 2012 / .NET 4.5.

The service is following the scheme of the code snippet below:

  • Using a timer
  • Exec
相关标签:
2条回答
  • 2020-12-31 06:34

    As a test case, I put a call to System.Threading.Thread.Sleep(500000) in the OnStop() callback of my Windows service. I started the service and then stopped it. I got the window with the progress bar indicating that the Service Control Manager (SCM) was attempting to stop the service. After about 2 minutes, I got this response from the SCM:

    enter image description here

    After I dismissed this window, the status of my service in the SCM changed to Stopping, and I noticed that the service continued to run in Task Manager. After the sleep elapsed (nearly 6 minutes later), the process stopped. Refreshing the SCM window showed the service was no longer running.

    I take a couple of things away from this. First, OnStop() should really attempt to stop the service in a timely manner just as part of playing nice with the system. Second, depending on how your OnStop() method is structured, you could force the service to ignore a preemptive request to stop, instead stopping when you say so. This is not recommended, but it appears that you could do this.

    As to your particular situation, the thing you have to understand is that the System.Timers.Timer.Elapsed event fires on a ThreadPool thread. By definition, this is a background thread, which means that it will not keep the application running. When the service is told to shut down, the system will stop all background threads and then exit the process. So your concern about keeping the processing going until it is finished despite being told by the SCM to shutdown cannot occur the way you've got things structured currently. To do that, you'd need to create a formal System.Threading.Thread object, set it as a foreground thread, and then use the timer to trigger this thread to execute (as opposed to being done in the Elapsed callback).

    All of that said, I still think you'll want to play nicely with the system, which means timely shutdown of the service when requested to do so. What happens if, for example, you need to reboot the machine? I haven't tested it, but if you force your service to continue running until the processing is complete, the system may indeed wait until the process finishes before actually restarting. That's not what I would want from my service.

    So I would suggest one of two things. The first option would be to break the processing into distinct chunks that can be done individually. As each chunk is finished, check to see if the service is stopping. If so, exit the thread gracefully. If this cannot be done, then I would introduce something akin to transactions to your processing. Let's say that you're needing to interact with a bunch of database tables and interrupting the flow once it's started becomes problematic because the database may be left in a bad state. If the database system allows transactions, this becomes relatively easy. If not, then do all the processing you can in memory and commit the changes at the last second. That way, you only block shutting down while the changes are being committed as opposed to blocking for the entire duration. And for what it's worth, I do prefer using ManualResetEvent for communicating shutdown commands to threads.

    To avoid rambling any further, I'll cut it off here. HTH.

    EDIT:

    This is off the cuff, so I won't verify its accuracy. I'll fix any problem you (or others) may find.

    Define two ManualResetEvent objects, one for shutdown notification and one for processing notification, and the Thread object. Change the OnStart() callback to this:

    using System.Threading;
    using Timer = System.Timers.Timer; // both Threading and Timers have a timer class
    
    ManualResetEvent _shutdownEvent = new ManualResetEvent(false);
    ManualResetEvent _processEvent  = new ManualResetEvent(false);
    Thread _thread;
    Timer _oTimer;
    
    protected override void OnStart(string[] args)
    {
        // Create the formal, foreground thread.
        _thread = new Thread(Execute);
        _thread.IsBackground = false;  // set to foreground thread
        _thread.Start();
    
        // Start the timer.  Notice the lambda expression for setting the
        // process event when the timer elapses.
        int intDelai = Properties.Settings.Default.WatchDelay * 1000;
        _oTimer = new Timer(intDelai);
        _oTimer.AutoReset = false;
        _oTimer.Elapsed += (sender, e) => _processEvent.Set();
        _oTimer.Start();
    }
    

    Change your Execute() callback to something like this:

    private void Execute()
    {
        var handles = new WaitHandle[] { _shutdownEvent, _processEvent };
    
        while (true)
        {
            switch (WaitHandle.WaitAny(handles))
            {
                case 0:  // Shutdown Event
                    return; // end the thread
                case 1:  // Process Event
                    Process();
                    _processEvent.Reset();  // reset for next time
                    _oTimer.Start();        // trigger timer again
                    break;
            }
        }
    }
    

    Create the Process() method like this:

    private void Process()
    {
        try
        {
            // Do your processing here.  If this takes a long time, you might
            // want to periodically check the shutdown event to see if you need
            // exit early.
        }
        catch (Exception ex)
        {
            // Do your logging here...
    
            // You *could * also shutdown the thread here, but this will not
            // stop the service.
            _shutdownEvent.Set();
        }
    }
    

    Finally, in the OnStop() callback, trigger the thread to shutdown:

    protected override void OnStop()
    {
        _oTimer.Stop();  // no harm in calling it
        _oTimer.Dispose();
    
        _shutdownEvent.Set();  // trigger the thread to stop
        _thread.Join();        // wait for thread to stop
    }
    
    0 讨论(0)
  • 2020-12-31 06:47

    @Matt - thanks for the great code, really helpful. I found it worked even better if I added another test on _shutdownEvent:

    case 1:  // Process Event
                Process();
                if(_shutdownEvent.WaitOne(0)) break; // don't loop again if a shutdown is needed
    ...
    
    0 讨论(0)
提交回复
热议问题