Ответ 1
В качестве тестового примера я поместил вызов System.Threading.Thread.Sleep(500000)
в обратный вызов OnStop()
моей службы Windows. Я начал службу, а затем остановил ее. Я получил окно с индикатором выполнения, указывающим, что диспетчер управления службами (SCM) пытался остановить службу. Примерно через 2 минуты я получил ответ от SCM:
После того как я отклонил это окно, статус моей службы в SCM изменился на Stopping, и я заметил, что служба продолжала работать в диспетчере задач. После истечения сна (около 6 минут спустя) процесс прекратился. Обновление окна SCM показало, что служба больше не работает.
Я снимаю пару вещей. Во-первых, OnStop()
должен действительно попытаться своевременно остановить службу, как часть игры с системой. Во-вторых, в зависимости от того, как структурирован ваш метод OnStop()
, вы можете заставить службу игнорировать превентивный запрос на остановку, вместо этого останавливаясь, когда вы так говорите. Это не рекомендуется, но, похоже, вы можете это сделать.
Что касается вашей конкретной ситуации, то вам нужно понять, что событие System.Timers.Timer.Elapsed
запускается в потоке ThreadPool
. По определению это фоновый поток , что означает, что он не будет поддерживать приложение. Когда служба будет выключена, система остановит все фоновые потоки и выйдет из процесса. Поэтому ваша озабоченность по поводу продолжения обработки продолжается до тех пор, пока она не будет завершена, несмотря на то, что SCM сообщит об отключении не может, так как у вас есть структурированные в настоящее время вещи. Для этого вам нужно создать формальный объект System.Threading.Thread
, установить его как поток переднего плана, а затем использовать таймер для запуска этого потока для выполнения (в отличие от выполнения в обратном вызове Elapsed
).
Все сказанное, я все еще думаю, что вы хотите хорошо играть с системой, что означает своевременное завершение службы, когда этого требуют. Что произойдет, если, например, вам нужно перезагрузить компьютер? Я не тестировал его, но если вы заставляете службу продолжать работать до завершения обработки, система может действительно дождаться завершения процесса до фактического перезапуска. Это не то, что я хотел бы получить от службы.
Поэтому я бы предложил одну из двух вещей. Первый вариант - разбить обработку на отдельные куски, которые можно выполнить индивидуально. По завершении каждого фрагмента проверьте, остановлена ли служба. Если это так, выйдите из потока изящно. Если этого не сделать, то я бы представил что-то вроде транзакций для вашей обработки. Скажем, что вам нужно взаимодействовать с кучей таблиц базы данных и прерывать поток после его начала, становится проблематичным, потому что база данных может быть оставлена в плохом состоянии. Если система базы данных допускает транзакции, это становится относительно простым. Если нет, то сделайте всю обработку, которую вы можете в памяти, и зафиксируйте изменения в последнюю секунду. Таким образом, вы блокируете только блокировку, пока изменения выполняются, а не блокируются в течение всего времени. И для чего это стоит, я предпочитаю использовать ManualResetEvent
для передачи команд shutdown в потоки.
Чтобы не путаться дальше, я отключу его здесь. НТН.
EDIT:
Это от манжеты, поэтому я не буду проверять ее точность. Я исправлю любую проблему, которую вы (или другие) можете найти.
Определите два объекта 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
}