Как остановить рабочие потоки в многопоточной службе 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;

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

Ответ 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 для очистки перед выходом.