Почему моя переменная-счетчик не возвращается к 0 в многопоточном сценарии? - PullRequest
0 голосов
/ 29 сентября 2011

Я создаю базовый класс службы Windows для управления опросом таблицы расписания любой ожидающей задачи и их выполнением.

Служба Windows использует System.Timers.Timer для запуска опроса таблицы расписания.

Я устанавливаю ThreadPool.SetMaxThread на 10 до инициализации таймера.

    protected override void OnStart(string[] args)
    {
        ThreadPool.SetMaxThreads(10, 10);

        this._Timer = new System.Timers.Timer();
        this._Timer.Elapsed += new ElapsedEventHandler(PollWrapper);
        this._Timer.Interval = 100;
        this._Timer.Enabled = true;
    }

Метод делегата, вызываемый таймером, сохраняет количество запущенных потоков, чтобы его можно было использовать в методе OnStop () для ожидания завершения каждого потока перед удалением службы.

    private void PollWrapper(object sender, ElapsedEventArgs e)
    {
        numberOfRunningThreads++;

        try
        {
            this.Poll(sender, e);
        }
        catch (Exception exception)
        {
            //some error logging here
        }
        finally
        {
            numberOfRunningThreads--;
        }
    }




    protected override void OnStop()
    {
        this._Timer.Enabled = false;

        while (numberOfRunningThreads > 0)
        {
            this.RequestAdditionalTime(1000);
            Thread.Sleep(1000);
        }
    }

Часто служба не останавливается, когда я пытаюсь остановить ее из консоли управления службами Windows. Если я отлаживаю его и добавляю точку останова к методу OnStop (), я вижу, что это не потому, что numberOfRunningThreads привязано к числу больше 0 (часто намного больше 10!). Никакие задачи не выполняются, и он остается на этом номере навсегда!

Во-первых, я не понимаю, как это число может быть больше 10, хотя ThreadPool.SetMaxThreads должно ограничивать его 10?

Во-вторых, даже если бы я не установил максимальное количество потоков, я ожидал бы, что блок finally PollWrapper в конечном итоге вернет счетчик к 0. Если счетчик остается больше 0, это можно объяснить только с помощью наконец, блок не выполняется, верно !? Как это возможно?

И, наконец, не могли бы вы предложить другой способ ограничения опроса числом возможных одновременных запущенных потоков фиксированным числом (.NET 3.5)?

Большое спасибо.

UPDATE:

После прочтения комментариев Яхии о повторном входе и SetMaxThread я изменил PollWrapper так, чтобы он всегда ограничивал максимальное количество порождаемых запущенных потоков. Я все еще хочу убедиться, что Опрос реентерабелен.

private void PollWrapper(object sender, ElapsedEventArgs e)
        {
            lock(this)
            {
                if(this.numberOfRunningThreads < this.numberOfAllowedThreads)
                {
                    this.numberOfRunningThreads++;

                    Thread t = new Thread(
                        () =>
                        {
                            try
                            {
                                this.Poll(sender, e);
                            }
                            catch (Exception ex)
                            { 
                                //log exception
                            }
                            finally
                            {
                                Interlocked.Decrement(ref this.numberOfRunningThreads);
                            }
                        }
                    );
                    t.Start();
                }
            }

Ответы [ 3 ]

7 голосов
/ 29 сентября 2011

Да, есть ситуации, когда блок finally может никогда не запуститься.

  • Вы можете выключить компьютер до того, как будет наконец выполнено окончание.
  • Код в блоке try может никогда не произойтизавершение (например, бесконечный цикл).
  • Environment.FailFast не запускает блоки finally.
  • Серьезная ошибка во время выполнения может привести к сбою всего процесса без выполнения оператора finally.

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


Здесь, хотя ваша проблема кажетсячтобы вы использовали несколько потоков, но не синхронизировали доступ к общим переменным:

numberOfRunningThreads++;

Вам необходимо заблокировать общий объект здесь.

5 голосов
/ 29 сентября 2011

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

Вы можете использовать Interlocked.Increment, Interlocked.Decrement вместо ++ и ...

0 голосов
/ 03 октября 2011

Согласно запросу OP (см. Комментарии выше) в отношении обновленного вопроса / кода:

Оператор lock ведет себя как «критическая» секция, т. Е. Останавливает параллельное выполнение этого конкретного блока кода - параметр lock используется в качестве «объекта синхронизации», так что любой блок кода, окруженный lock для одной и той же переменной, не будет выполняться параллельно в другом потоке.

Оператор lock ничего не делает с переменной / ее содержимым - например, он не «замораживает» содержимое, поэтому его можно изменить в любом месте в любое время в любом исполняемом коде (параллельно), пока этот код не защищен lock и той же переменной, что и параметр.

Если вы посмотрите на предоставленную мною ссылку (ваши ссылки на устаревшую версию документации для VS2003), в ней прямо сказано, что блокировка (это), например, НЕ подходит для использования - причина и рекомендация описаны соответственно ...

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

...