Как безопасно остановить поток С#.NET в службе Windows?

Я поддерживаю некоторый код, который выглядит примерно так. Это служба Windows, которая выполняет некоторую работу каждые 30 минут. Метод ActualWorkDoneHere занимает около 30 секунд для запуска, но если он остановлен во время работы, он может оставить вещи в плохом состоянии. Каков наилучший способ предотвратить это? Должен ли я заменить значение While (true) на логическое значение, которое установлено в методе onstop (исключение прерывания потока)? Есть ли способ узнать, спал ли поток?

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

Ответы

Ответ 1

Когда у меня есть что-то вроде этого, я обычно использую ManualResetEvent. Это установлено в вызове Stop(). Затем я жду с таймаутом:

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

Ответ 2

Реализация его сама по себе является единственным безопасным вариантом. Даже если вы найдете способ узнать, спит нить, у вас все еще будет состояние гонки, если вы попытаетесь его убить (потому что он потенциально начинает обрабатывать после проверки и до того, как вы его убьете).

Вместо Thread.Sleep вы можете, например, спать 500 мс и проверить, остается ли флаг прерывания ложным, спать еще 500 мс и т.д. до того, как пройдет 30 минут, затем выполните задание и т.д. (это будет прагматичный подход). Если вы хотите что-то более элегантное, вы можете использовать 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();
        }
    }
}

Ответ 4

Вот один из способов сделать это. Добавьте в свой класс следующие переменные:

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

Затем в OnStart вы делаете что-то вроде этого (у меня есть вспомогательный метод, который выполняет некоторый журнал в этом примере, а метод "Run" выполняет фактическую работу).:

    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."));
    }

Ваш метод "Запуск", который выполняет работу потока, будет выглядеть так (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() занимает слишком много времени, окна сообщают о том, что служба перестает останавливаться.

Ответ 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();

            }
        }

    }
}

Cheers, Бхарат.

Ответ 7

Моя служба прослушивает сетевой сокет, так что я сделал, это создать объединенную пару сетевых сокетов и использовать системный вызов select для прослушивания на обоих. Если объединенная пара сообщила, что готова к чтению, я знал, что отключил эту услугу.

Этот трюк может использоваться для запуска произвольного количества потоков для закрытия, пока никто из них не будет фактически прочитан из подключенной пары.