Как остановить рабочие потоки в многопоточной службе Windows при остановке службы
У меня есть служба Windows, которая использует модель очереди производителей/потребителей с несколькими рабочими потоками, обрабатывающими задачи с очереди. Эти задачи могут выполняться очень долго, в течение нескольких минут, если не часов, и не включают циклы.
Мой вопрос - это лучший способ справиться с остановкой службы, чтобы изящно завершить обработку этих рабочих потоков. Я прочитал в другом вопросе qaru.site/info/27770/..., что использование thread.Abort() является признаком плохого дизайна, но кажется, что для метода OnStop() предоставляется только ограниченный количество времени до завершения обслуживания. Я могу сделать достаточную очистку в catch для ThreadAbortException (нет опасности несогласованного состояния), поэтому вызов thread.Abort() в рабочих потоках кажется мне удобным. Это? Каковы альтернативы?
Ответы
Ответ 1
В самом деле, Abort
следует избегать. Было бы лучше дать им некоторое время, чтобы выйти изящно - тогда, возможно, после таймаута может рассмотреть возможность прерывания их, но в конечном итоге остановка службы может сделать это точно, убив процесс вместо этого. p >
Я попытался бы получить сигнал в моих очередях, который говорит "flush and exit" - как и метод Close
здесь, но с каким-то сигналом при завершении.
Если вы прибегли к Abort
- рассмотрите процесс, смертельно раненный. Убейте его как можно скорее.
Ответ 2
Создайте тип задачи для "shutdown" и введите его в очередь производителя/пользователя один раз для каждого рабочего потока.
Затем используйте Thread.Join(с таймаутом), чтобы завершить завершение.
Ответ 3
С .NET 4.0 вы можете использовать пространство имен System.Threading.Tasks
, чтобы использовать объект Task
. В двух словах вы можете назначить CancellationToken
более грациозно обрабатывать отмены/прерывания в задачах, будь то длинные или короткие.
Подробнее см. здесь для более подробной информации из MSDN.
Ответ 4
Вопрос с поправками на самом деле имеет меньше общего с потоками и больше связан с тем, как остановить длительные действия. Лично я всегда использую APM для длительных потоков и коммуникационных мероприятий, таких как большие передачи файлов. Каждый обратный вызов выполняется в потоке пула завершения ввода-вывода и быстро завершается, обрабатывает небольшой фрагмент и назначает следующий проход. Ожидающие операции можно отменить просто, вызвав Close()
в объекте сокета. Это намного дешевле и эффективнее, чем управление потоками DIY.
Как уже упоминалось, Abort()
- плохая карма, и ее следует избегать.
Материал, приведенный ниже, был написан до исключения цикла из вопроса.
При длительном цикле процессов все они должны включать флаг завершения в их состояние цикла, чтобы вы могли сигнализировать им о выходе.
bool _run; //member of service class
//in OnStart
_run = true;
//in a method on some other thread
while ((yourLoopCondition) & _run)
{
//do stuff
foreach (thing in things)
{
//do more stuff
if (!_run) break;
}
}
if (!_run) CleanUp();
//in OnStop
_run = false;
Строго вы должны использовать летучие, но поскольку только логика управления устанавливает флаг, это не имеет значения. Технически существует условие гонки, но это означает, что вы можете пойти еще раз.
Ответ 5
Используйте ManualResetEvent, чтобы проверить, сигнализируется ли событие, см. образец Синхронизация потоков (Руководство по программированию на С#)
Ответ 6
Вот код, который я использую для остановки потоков в службе Windows (помните, что я напрямую использую Threads и не использую пулы потоков):
// signal all threads to stop
this.AllThreadsStopSignal.Set();
if (logThreads.IsDebugEnabled)
logThreads.Debug ("Stop workers");
// remember the time of the signal
DateTime signalTime = DateTime.Now;
// create an array of workers to be stopped
List<IServiceWorker> workersToBeStopped = new List<IServiceWorker> (workers);
while (true)
{
// wait for some time
Thread.Sleep (1000);
// go through the list and see if any workers have stopped
int i = 0;
while (i < workersToBeStopped.Count)
{
IServiceWorker workerToBeStopped = workersToBeStopped [i];
if (log.IsDebugEnabled)
log.Debug (String.Format (System.Globalization.CultureInfo.InvariantCulture,
"Stopping worker '{0}'. Worker state={1}",
workerToBeStopped.WorkerDescription,
workerToBeStopped.WorkerState));
bool stopped = workerToBeStopped.JoinThread (TimeSpan.Zero);
// if stopped, remove it from the list
if (stopped)
{
workersToBeStopped.RemoveAt (i);
if (log.IsDebugEnabled)
log.Debug (String.Format (System.Globalization.CultureInfo.InvariantCulture,
"Worker '{0}' was stopped.", workerToBeStopped.WorkerDescription));
}
else
{
i++;
if (log.IsDebugEnabled)
log.Debug (String.Format (System.Globalization.CultureInfo.InvariantCulture,
"Worker '{0}' could not be stopped, will try again later. Worker state={1}",
workerToBeStopped.WorkerDescription,
workerToBeStopped.WorkerState));
}
}
// if all workers were stopped, exit from the loop
if (workersToBeStopped.Count == 0)
break;
// check if the duration of stopping has exceeded maximum time
DateTime nowTime = DateTime.Now;
TimeSpan duration = nowTime - signalTime;
if (duration > serviceCustomization.ThreadTerminationMaxDuration)
{
// execute forced abortion of all workers which have not stopped
foreach (IServiceWorker worker in workersToBeStopped)
{
try
{
log.Warn (String.Format (System.Globalization.CultureInfo.InvariantCulture,
"Aborting worker '{0}.", worker.WorkerDescription));
worker.Abort ();
log.Warn (String.Format (System.Globalization.CultureInfo.InvariantCulture,
"Worker '{0}' aborted.", worker.WorkerDescription));
}
catch (ThreadStateException ex)
{
log.Warn (String.Format (System.Globalization.CultureInfo.InvariantCulture,
"Worker '{0}' could not be aborted.", worker.WorkerDescription), ex);
}
}
break;
}
}
Ответ 7
Если ваш процесс все равно завершается, я лично не вижу проблемы с использованием Abort(). Я попытался бы найти другой способ, но в конце концов, это не имеет значения , если вы все равно будете делать очистку в основном потоке.
Другой вариант - отметить ваши рабочие потоки как background темы. Таким образом, они автоматически закрываются, когда процесс завершается. Возможно, вы сможете использовать событие AppDomain.ProcessExit для очистки перед выходом.