Windows服务OnStop等待处理完成

16

我实际上正在使用VS 2012 / .NET 4.5中开发Windows服务。

该服务遵循以下代码片段的方案:

  • 使用计时器
  • 每隔几分钟执行一些所需操作。
  • 该进程需要大约10分钟才能完成。
  • 在服务中使用单个线程。

我担心的是,如果有人通过管理控制台停止该服务,可能正好停在服务正在进行的过程中。

我已经阅读了一些关于使用请求停止停止Windows服务的文章,但有点困惑。有时会创建WorkerThreads,有时会创建ManualResetEvents,但直到现在,我还无法完全掌握我的Windows服务的最佳方法。

在onStop方法中,在处理完成之前,我需要等待处理完毕后再停止Windows服务。

那么,考虑到下面的代码片段,最佳的前进方式是什么?

谢谢大家!

namespace ImportationCV
{
    public partial class ImportationCV : ServiceBase
    {
        private System.Timers.Timer _oTimer;       

        public ImportationCV()
        {
            InitializeComponent();

            if (!EventLog.SourceExists(DAL.Utilities.Constants.LOG_JOURNAL))
            {
                EventLog.CreateEventSource(DAL.Utilities.Constants.LOG_JOURNAL,     DAL.Utilities.Constants.SOURCE_JOURNAL);
            }

            EventLog.Source = DAL.Utilities.Constants.SOURCE_JOURNAL;
            EventLog.Log = DAL.Utilities.Constants.LOG_JOURNAL;
        }

        protected override void OnStart(string[] args)
        {            
            int intDelai = Properties.Settings.Default.WatchDelay * 1000;

            _oTimer = new System.Timers.Timer(intDelai);
            _oTimer.Elapsed += new ElapsedEventHandler(this.Execute);

            _oTimer.Start();           

            EventLog.WriteEntry(DAL.Utilities.Constants.LOG_JOURNAL, "Service " + DAL.Utilities.Constants.SERVICE_TITLE + " started at " + DateTime.Now.ToString("HH:mm:ss"), EventLogEntryType.Information);
        }

        protected override void OnStop()
        {

            if (_oTimer != null && _oTimer.Enabled)
            {
                _oTimer.Stop();
                _oTimer.Dispose();
            }

            EventLog.WriteEntry(DAL.Utilities.Constants.LOG_JOURNAL, "Service " + DAL.Utilities.Constants.SERVICE_TITLE + " stopped at " + DateTime.Now.ToString("HH:mm:ss"), EventLogEntryType.Information);
        }

        private void Execute(object source, ElapsedEventArgs e)
        {
            _oTimer.Stop();

            try
            {                
                //Process


            }
            catch (Exception ex)
            {
                EventLog.WriteEntry(DAL.Utilities.Constants.LOG_JOURNAL, (ex.StackTrace + ("\r\n" + ex.Message)), EventLogEntryType.Error);
            }

            _oTimer.Start();
        }
    }
}

我最近一直在尝试这个。虽然你可以使用“RequestAdditionalTime”请求更多时间,但没有办法让Windows给你完整的10分钟来停止。我相信你能得到的最多只有2分钟。 - pquest
1
我理解。不幸的是,Windows 不允许这样做。如果它没有在截止日期之前完成,Windows 将终止该进程。据我所知,没有办法阻止这种情况发生。 - pquest
如果我想要做到那个,我认为我需要使用线程,但在我的情况下这不是最好的方式? - crisjax
2
您的服务需要能够优雅地处理不完整的运行。每次启动服务时,它都需要进行清理。用户可以拔掉电源线。 - paparazzo
你有代码示例吗,以便了解如何在我的代码中实现它? - crisjax
显示剩余2条评论
2个回答

23

作为一个测试用例,我在我的Windows服务的OnStop()回调中调用了System.Threading.Thread.Sleep(500000)。我启动了这个服务并停止它,然后我得到了显示服务控制管理器(SCM)正在尝试停止服务的进度条窗口。大约2分钟后,我从SCM得到了这个响应:

enter image description here

当我关闭这个窗口时,SCM中我的服务的状态变为正在停止,我注意到服务在任务管理器中继续运行。当休眠结束(将近6分钟后),进程停止。刷新SCM窗口会显示此服务已不再运行。

我从中得到了几个结论。首先,OnStop()应该尽快尝试停止服务,以便与系统友好互动。其次,取决于OnStop()方法的结构,你可能会强制服务忽略预先停止的请求,在你说停止之前停止。这是不推荐的,但是看起来你可以这样做。

至于你的特定情况,你必须明白System.Timers.Timer.Elapsed事件在线程池线程上运行。根据定义,这是一个后台线程,这意味着它不会保持应用程序运行。当告诉服务关闭时,系统将停止所有后台线程,然后退出进程。所以你关心的问题是如何在被告知要关闭的情况下保持处理直到完成,在当前结构下是不可能的。为了达到这个目的,你需要创建一个正式的System.Threading.Thread对象,将其设置为前台线程,然后使用计时器触发此线程执行(而不是在Elapsed回调中完成)。

尽管如此,我仍然认为你应该与系统友好互动,这意味着在请求停止服务时及时关闭服务。例如,如果你需要重新启动机器会发生什么?我没有测试过,但是如果你强制服务继续运行,直到处理完成,那么在实际重启之前,系统可能会等待进程完成。这不是我希望从我的服务得到的结果。

因此,我建议两种方案。第一种选择是将处理分成可以单独完成的不同块。每完成一个块,检查服务是否正在停止。如果是,则优雅地退出线程。如果做不到这一点,那么我会在处理中引入类似于事务的东西。假设你需要与许多数据库表交互,并且一旦开始中断流程就变得有问题,因为数据库可能处于错误状态。如果数据库系统允许事务,则变得相对容易。如果不行,那么在内存中完成尽可能多的处理,并在最后一刻提交更改。这样,只有在提交更改时才会阻止关闭,而不是在整个过程中阻止。顺便说一句,我更喜欢使用ManualResetEvent来向线程发送关闭命令。

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();
}

将您的Execute()回调更改为以下内容:

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;
        }
    }
}

请按照以下方式创建Process()方法:

Create the Process() method like this:

-->

创建Process()方法的代码如下:

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();
    }
}

最后,在OnStop()回调中触发线程关闭:

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
}

谢谢你的回答。你有代码示例吗,以便我了解如何在我的代码中实现它? - crisjax
我添加了一些内容来帮助你开始。 - Matt Davis
2
我严重怀疑重新启动会等待进程停止。病毒可以利用这一点来阻止重新启动。停止中的错误可能会阻止重新启动。https://dev59.com/uUvSa4cB1Zd3GeqPcCVA - paparazzo
@Blam,就像我说的那样,我还没有测试过。好链接。 - Matt Davis

0

@Matt - 感谢你提供的优秀代码,非常有帮助。 我发现如果在 _shutdownEvent 上添加另一个测试,它的效果会更好:

case 1:  // Process Event
            Process();
            if(_shutdownEvent.WaitOne(0)) break; // don't loop again if a shutdown is needed
...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接