Ожидание остановки потоков в службах Windows - PullRequest
1 голос
/ 06 апреля 2011

Я работаю над своим первым проектом Windows Service, который включает в себя некоторые основные потоки / параллелизм. Пока это было довольно интенсивно, но я постепенно начинаю понимать многопоточность (я думаю, мы посмотрим на это ...).

У меня есть служба Windows, которая будет содержать 2 слабо связанных рабочих потока. Поток A будет загружать файлы с FTP-сервера, разархивировать / подготавливать их и сохранять в другом каталоге, где поток B будет наблюдать за FileSystemWatcher. Поток B проанализирует файлы, выполнит несколько действий с данными, сделает несколько http-вызовов и, наконец, заархивирует и удалит файлы из рабочего каталога.

Я только начинаю работу, и проблема, с которой я сейчас сталкиваюсь, заключается в том, как дождаться возврата как потока A, так и потока B. Я посмотрел на этот вопрос: Thread.Join на несколько потоков с таймаутом и у меня была идея.

Проблема в том, что если мы ожидаем, что потоки x вернутся с секундным таймаутом ax для каждого из них, при достаточном количестве потоков сервис может оказаться невосприимчивым даже при нормальной работе (я прочитал тайм-аут до того, как SCM жалуется, составляет 30 секунд, верно ?). Кроме того, если мы решим эту проблему, отслеживая время, оставшееся до присоединения при циклическом переключении потоков, чем дольше мы ждем потоки в начале сбора рабочих, тем меньше времени остается для возврата оставшихся потоков - так что в конечном итоге при достаточном количестве потоков служба будет не отвечать, даже если все потоки возвращаются в течение ожидаемого периода времени.

В этом случае я, вероятно, просто буду нитью. Присоединяюсь и к А, и к В по истечении 14 секунд, потому что у меня всего два рабочих, а 14 секунд кажется достаточным временем для возвращения обоих.

Если бы у меня было переменное количество рабочих потоков (скажем,> 8), стоило бы сделать что-то подобное? Будет ли это работать надежно?

Примечание: не используйте следующее - это плохая идея на нескольких уровнях. Смотрите ответы

    protected override void OnStop()
    {
        // the service is stopping - set the event
        Worker.ThreadStopEvent.Set();

        // stop the threads
        Parallel.ForEach(_workerThreads, t =>
        {
            t.Join(new TimeSpan(0, 0, 28));
        });
    }

Ответы [ 3 ]

1 голос
/ 06 апреля 2011

Ваш текущий подход может не сработать - у вас нет никакого контроля над тем, сколько потоков действительно создает Parallel.ForEach, не все объединения потоков могут выполняться параллельно - все зависит от того, как эти задачи запланированы в пуле потоков. , Кроме того, это очень неэффективно, так как все потоки, которые вы создаете, просто ждут.

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

1 голос
/ 06 апреля 2011

Технически вам не нужно ждать завершения потоков (Join), но пока они не сообщат о завершении.Таким образом, вы можете использовать экземпляр CountdownEvent , общий для всех потоков, и ждать его.Все потоки должны были бы уменьшить событие в блоке finally:

void WorkerThread (object args)
{
  try
  {
    // actual work here
    ... 
  }
  finally
  {
    sharedCountdown.Signal();
  }
}

, а при завершении работы и ожидании завершения потоков:

// Notify all workers of shutdown
...

// Now wait for all workers to complete, up to 30 seconds
sharedCountdown.Wait(TimeSpan.FromSeconds(30));
1 голос
/ 06 апреля 2011

Я бы предложил, чтобы вместо использования тайм-аута для каждого потока вы устанавливали событие, а затем периодически делали Join с более коротким тайм-аутом.Экстремальная версия будет выглядеть так:

Worker.ThreadStopEvent.Set();
Thread.Sleep(30000); // wait 30 seconds
// Now try to Join each thread, with a very short (20 ms, maybe) timeout.

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

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

Возможно, вам лучше всего использовать CountdownEvent .Каждый поток сигнализирует, когда он остановлен, и основной поток ожидает, пока все потоки не будут остановлены.Метод Wait позволяет указать значение времени ожидания.После ожидания вы можете определить, зависли ли какие-либо потоки, вызвав Join с очень коротким таймаутом.

...