16

我实际上在 VS 2012 / .NET 4.5 中开发了一个 Windows 服务。

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

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

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

我已经阅读了一些关于通过请求停止来停止 Windows 服务的内容,但有点迷失了。有时会创建 WorkerThreads,有时会创建 ManualResetEvents,但到目前为止,我还不能完全掌握我的 Windows 服务的最佳前进方向。

在停止 Windows 服务之前,我需要等到 onStop 方法中的处理正确完成。

那么,最好的方法是什么,同时考虑下面的代码片段?

谢谢大家!

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

2 回答 2

21

作为测试用例,我在Windows 服务System.Threading.Thread.Sleep(500000)的回调中调用了 to。OnStop()我启动了服务,然后停止了它。我得到带有进度条的窗口,表明服务控制管理器 (SCM) 正在尝试停止服务。大约 2 分钟后,我收到了 SCM 的回复:

在此处输入图像描述

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

我从这里拿走了几件事。首先,OnStop()应该真正尝试及时停止服务,作为与系统相处融洽的一部分。其次,根据您的OnStop()方法的结构,您可以强制服务忽略先发制人的停止请求,而不是在您这样说时停止。不建议这样做,但您似乎可以这样做。

至于您的特殊情况,您必须了解的是System.Timers.Timer.Elapsed事件ThreadPool线程上触发。根据定义,这是一个后台线程,这意味着它不会让应用程序继续运行。当服务被告知关闭时,系统将停止所有后台线程,然后退出进程。因此,尽管 SCM 告知您要关闭,但您对继续处理直到完成的担忧不会像您目前的结构那样发生。为此,您需要创建一个正式System.Threading.Thread对象,将其设置为前台线程,然后使用计时器触发该线程执行(而不是在Elapsed回调中完成)。

综上所述,我仍然认为你会想要很好地使用系统,这意味着在请求时及时关闭服务。例如,如果您需要重新启动机器会发生什么?我还没有测试过,但是如果你强制你的服务继续运行直到处理完成,系统可能确实会等到进程完成才真正重新启动。这不是我想要的服务。

所以我会建议两件事之一。第一种选择是将处理分成可以单独完成的不同块。每个块完成后,检查服务是否正在停止。如果是这样,请优雅地退出线程。如果无法做到这一点,那么我会在您的处理中引入类似于交易的东西。假设您需要与一堆数据库表进行交互,并且一旦开始就中断流程会出现问题,因为数据库可能处于错误状态。如果数据库系统允许事务,这变得相对容易。如果没有,则在内存中执行所有处理并在最后一秒提交更改。这样,您只会在提交更改时阻止关闭,而不是在整个持续时间内阻止。ManualResetEvent用于将关闭命令传递给线程。

为避免再啰嗦,我就在这里删掉。HTH。

编辑:

这是即兴的,所以我不会验证它的准确性。我会解决您(或其他人)可能发现的任何问题。

定义两个ManualResetEvent对象,一个用于关闭通知,一个用于处理通知,以及Thread对象。OnStart()将回调更改为:

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()方法:

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
}
于 2014-03-20T15:00:00.623 回答
0

@Matt - 感谢伟大的代码,真的很有帮助。如果我在 _shutdownEvent 上添加另一个测试,我发现效果会更好:

case 1:  // Process Event
            Process();
            if(_shutdownEvent.WaitOne(0)) break; // don't loop again if a shutdown is needed
...
于 2014-11-18T11:27:52.817 回答