【问题标题】:How do I safely stop a C# .NET thread running in a Windows service?如何安全地停止在 Windows 服务中运行的 C# .NET 线程?
【发布时间】:2009-11-19 17:04:17
【问题描述】:

我正在维护一些看起来像这样的代码。这是一项 Windows 服务,每 30 分钟执行一次。 ActualWorkDoneHere 方法运行大约需要 30 秒,但如果在运行时停止它可能会使事情处于不良状态。防止这种情况发生的最好方法是什么?我应该用 onstop 方法中设置为 false 的布尔值替换 While(true)(删除线程 Abort 调用)吗?有什么方法可以判断线程是否处于休眠状态?

namespace WorkService
{
    public partial class WorkService : ServiceBase
    {
        private Thread _workerThread = null;

        public WorkService()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            _workerThread = new Thread(new ThreadStart(DoWork));
            _workerThread.Start();
        }

        protected override void OnStop()
        {
            _workerThread.Abort();
        }

        static void DoWork()
        {
            int sleepMinutes = 30;

            while (true)
            {
                 ActualWorkDoneHere();

                 System.Threading.Thread.Sleep(new TimeSpan(0, sleepMinutes, 0));
            }
        }
    }
}

【问题讨论】:

  • 这看起来像是任务计划程序的工作! :)
  • @Greg 任务计划程序不是按计划运行吗?在这种情况下,Stimy 希望暂停 30 分钟然后再次运行,而不是每 30 分钟运行一次。

标签: c# .net windows-services


【解决方案1】:

当我有这样的事情时,我通常使用ManualResetEvent。这是在Stop() 调用中设置的。然后我等待超时:

for (;;)
{
    if (_stop.WaitOne(timeout))
        break;
    DoSomething();
}

【讨论】:

  • +1 此外,如果您可以在几个点停止 DoSomething() 而不会使数据处于不一致状态,您可以在那里执行if (_stop.WaitOne (0)) { ...return...} 以避免等待长达 30 秒。
  • +1 当线程真正停止时,我使用 ManualResetEvent 触发信号。所以 stop 方法在告诉线程停止之后会等待这个信号。
  • 你不需要一个事件来告诉你线程何时停止;只需调用 Thread.Join。
  • 如果您使用的是 .NET 4.0,则可以使用 CancellationToken
【解决方案2】:

自己实施是唯一安全的选择。即使你找到了一种方法来确定一个线程是否正在休眠,如果你试图杀死它,你仍然会遇到竞争条件(因为它可能在你检查之后和你杀死它之前开始处理)。

你可以代替 Thread.Sleep,例如睡眠 500 毫秒并检查中止标志是否仍然为假,在 30 分钟过去之前再睡眠 500 毫秒等,然后执行工作等(这将是一种务实的方法)。如果你想要更优雅的东西,你可以使用带超时的 ManualResetEvent 来等待主线程发出中止时间的信号。

【讨论】:

    【解决方案3】:

    哇,每个人都让这变得如此复杂。使用计时器:

    在比赛中: 原始帖子在 OnStop 中进行了一场比赛,该比赛已得到修复。据我所知,将服务置于停止状态不会中止用于服务计时器的线程池线程。计时器触发和服务同时停止的条件无关紧要。 ActualWorkDoneHere() 将运行或不运行。两者都是可接受的条件。

    namespace WorkService
    {
        public partial class WorkService : ServiceBase
        {
            protected const int sleepMinutes = 30;
            protected System.Timers.Timer _interval;
            protected bool _running = false;
    
            public WorkService()
            {
                InitializeComponent();
                _interval = new System.Timers.Timer();
                _interval.Elapsed += new ElapsedEventHandler(OnTimedEvent);
                _interval.Interval = sleepMinutes * 60 * 1000;
                _running = false;
            }
    
            protected override void OnStart(string[] args)
            {
                _running = true;
                _interval.Enabled = true;
            }
    
            protected override void OnStop()
            {
                _interval.Enabled = false;
                _running = false;
            }
    
            private static void OnTimedEvent(object source, ElapsedEventArgs e)
            {
                if(_running)
                    ActualWorkDoneHere();
            }
        }
    }
    

    【讨论】:

    • 如果有人仍然觉得存在竞争条件,请解释一下。
    • @JeffreyABecker 默认情况下,计时器可能同时运行多个线程,因此存在竞争条件的可能性。 ManualResetEvent 总是默认在一个线程上运行。
    【解决方案4】:

    这是一种方法。将以下变量添加到您的类中:

    private readonly object syncObject = new object();
    private bool stopping;
    private bool stopped = true;
    

    然后在 OnStart 中,你做这样的事情(我有一个辅助方法在这个例子中做一些记录,而“运行”方法做实际的工作)。:

        public override void OnStart()
        {
            while (stopping)
            {
                Thread.Sleep(MSECS_SLEEP_FOR_STOP);
            }
    
            lock (syncObject)
            {
                // make sure task isn't already started
                if (!stopped)
                {
                    Helper.WriteToLog(logger, Level.INFO,
                        string.Format("{0} {1}", TASK_NAME, "is already started."));
                    return;
                }
                stopped = false;
            }
    
            // start task in new thread
            Thread thread = new Thread(Run);
            thread.Start();
    
            Helper.WriteToLog(logger, Level.INFO,
                string.Format("{0} {1}", TASK_NAME, "was started."));
        }
    

    执行线程工作的“Run”方法看起来像这样(processInterval 是您希望在两次运行之间等待多长时间,您可以在构造函数中设置它或直接对其进行硬编码):

        private void Run()
        {
            try
            {
                while (!stopping)
                {
                    // do work here
    
                    // wait for process interval
                    DateTime waitStart = DateTime.Now;
                    while (((DateTime.Now - waitStart).TotalMilliseconds < processInterval) && !stopping)
                    {
                        // give processing time to other threads
                        Thread.Sleep(MSECS_SLEEP_FOR_CHECK);
                    }
                }
                lock (syncObject)
                {
                    stopped = true;
                    stopping = false;
                }
    
                Helper.WriteToLog(logger, Level.INFO,
                    string.Format("{0} {1}", TASK_NAME, "was stopped."));
            }
            catch (Exception e)
            {
                // log the exception, but ignore it (i.e. don't throw it)
                Helper.LogException(logger, MethodBase.GetCurrentMethod(), e);
            }
        }
    

    然后在 OnStop 中,您会这样做:

        public override void OnStop()
        {
            lock (syncObject)
            {
                if (stopping || stopped)
                {
                    Helper.WriteToLog(logger, Level.INFO,
                        string.Format("{0} {1}", TASK_NAME, "is already stopped."));
                    return;
                }
                stopping = true;
            }
        }
    

    【讨论】:

      【解决方案5】:

      您可以使用锁定对象来防止线程在您的工作实际发生时停止......

          private static readonly object _syncRoot = new object();
      
          protected override void OnStop()
          {
              lock (_syncRoot) 
              {
                  _workerThread.Abort();
              }
          }
      
          static void DoWork()
          {
              int sleepMinutes = 30;
      
              while (true)
              {
                   lock (_syncRoot) 
                   {
                       ActualWorkDoneHere();
                   }
      
                   System.Threading.Thread.Sleep(new TimeSpan(0, sleepMinutes, 0));
              }
          }
      

      但您应该小心 - 如果您的 ActualWorkDoneHere() 函数耗时过长,Windows 将报告该服务未能停止。

      【讨论】:

        【解决方案6】:

        尝试使用自动重置标志来处理服务的停止。在这种情况下,您不必执行线程中止。已在下面添加示例代码

        namespace WorkService
        {
            public partial class WorkService : ServiceBase
            {
            AutoResetEvent serviceStopEvent = new AutoResetEvent( false);
        
                public WorkService()
                {
                    InitializeComponent();
                }
        
                protected override void OnStart(string[] args)
                {
                    Thread workerThread = new Thread(new ThreadStart(DoWork));
                    workerThread.Start();
                }
        
                protected override void OnStop()
                {
                   serviceStopEvent.Set();
                }
        
                static void DoWork()
                {
                    int sleepMinutes = 30;
                WaitHandle[ ] handles = new WaitHandle[ ] { serviceStopEvent };
        
                    while (WaitHandle.WaitAny( handles))
                    {
                         ActualWorkDoneHere();
        
                    }
                }
        
            }
        }
        

        干杯, 巴拉特。

        【讨论】:

        • -1 AutoResetEvent 在停止之前将始终未设置,并且永远不会运行 ActualWorkDoneHere()。
        • 糟糕。忘记在 start 中添加重置事件。
        【解决方案7】:

        我的服务侦听网络套接字,所以我所做的是创建一对连接的网络套接字并使用 select 系统调用来侦听两者。如果加入的对报告已准备好阅读,我知道要关闭服务。

        这个技巧可以用来触发任意数量的线程关闭,只要它们都没有真正从连接的对中读取。

        【讨论】:

          【解决方案8】:
          while (true)
                  {
                      if (m_reset.WaitOne(1,false))
                          break;
                       // DoSomething
          
          
                  }
          

          请在onStop()里面试试这个

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2010-12-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-05-12
            • 2012-05-25
            相关资源
            最近更新 更多