Как остановить рабочие потоки в многопоточной службе Windows при остановке службы - PullRequest
10 голосов
/ 01 апреля 2009

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

Мой вопрос о том, как лучше всего обработать остановку службы, чтобы постепенно завершить обработку этих рабочих потоков. В другом SO вопросе я прочитал, что использование thread.Abort () является признаком плохого дизайна, но, похоже, что методу OnStop () сервиса дается только ограниченное количество времени для завершения до сервиса прекращено. Я могу сделать достаточную очистку в улове для ThreadAbortException (нет опасности несовместимого состояния), поэтому вызов thread.Abort () в рабочих потоках мне кажется нормальным. Это? Какие есть альтернативы?

Ответы [ 7 ]

6 голосов
/ 01 апреля 2009

Действительно, Abort следует избегать. Лучше всего дать им время для грациозного выхода - тогда, возможно, по истечении времени ожидания , может быть рассмотрит возможность их прерывания, - но в конечном итоге остановка службы может сделать то же самое, просто остановив процесс.

Я бы попытался иметь сигнал в своих очередях, который говорит "сбросить и выйти" - очень похоже на Close метод здесь , но с каким-то сигналом, когда он завершен.

Если вы прибегаете к Abort - считайте процесс смертельно раненым. Убей его как можно скорее.

5 голосов
/ 01 апреля 2009

Создайте тип задачи для «выключения» и вставьте его в очередь производителя / потребителя один раз для каждого рабочего потока.

Затем используйте Thread.Join (с таймаутом), чтобы убедиться, что завершение завершено.

2 голосов
/ 13 августа 2010

В .NET 4.0 вы можете использовать пространство имен System.Threading.Tasks, чтобы использовать объект Task. В двух словах, вы можете назначить CancellationToken для более изящной обработки отмен / прерываний в задачах, будь то длительное или короткое выполнение.

См. здесь для получения дополнительной информации из MSDN.

1 голос
/ 01 апреля 2009

Вопрос с внесенными в него поправками на самом деле связан не с многопоточностью, а с тем, как остановить длительные действия. Лично я всегда использую APM для длительных потоковых и коммуникационных операций, таких как передача больших файлов. Каждый обратный вызов выполняется в потоке пула завершения ввода-вывода и быстро завершается, обрабатывая скромный кусок и планируя следующий проход. Отложенные операции можно отменить, просто вызвав Close() для объекта сокета. Это намного дешевле и эффективнее, чем самостоятельное управление потоками.


Как уже упоминалось, Abort() - это плохая карма, и ее следует избегать.

Материал ниже был написан до исключения случая зацикливания из вопроса.

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

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;

Строго говоря, вы должны использовать летучие вещества, но поскольку только логика управления устанавливает флаг, это не имеет значения. Технически, существует состояние гонки, но это просто означает, что вы можете объехать еще раз.

0 голосов
/ 01 апреля 2009

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

Другой вариант - пометить ваши рабочие потоки как background threads. Таким образом, они закрываются автоматически при закрытии процесса. Возможно, вы сможете использовать событие AppDomain.ProcessExit для очистки перед выходом.

0 голосов
/ 01 апреля 2009

Вот код, который я использую для остановки потоков в службе Windows (учтите, что я использую потоки напрямую, а не пулы потоков):

// 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;
    }
}
0 голосов
/ 01 апреля 2009

Используйте ManualResetEvent, чтобы проверить, сигнализируется ли событие, см. Пример в Синхронизация потоков (Руководство по программированию в C #)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...