2016-09-15 2 views
6

У меня есть служба Windows, которая каждые 5 секунд проверяет работу. Он использует System.Threading.Timer для обработки проверки и обработки и Monitor.TryEnter, чтобы убедиться, что только один поток проверяет работу.Monitor.TryEnter и Threading.Timer состояние гонки

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

readonly object _workCheckLocker = new object(); 

public Timer PollingTimer { get; private set; } 

void InitializeTimer() 
{ 
    if (PollingTimer == null) 
     PollingTimer = new Timer(PollingTimerCallback, null, 0, 5000); 
    else 
     PollingTimer.Change(0, 5000); 

    Details.TimerIsRunning = true; 
} 

void PollingTimerCallback(object state) 
{ 
    if (!Details.StillGettingWork) 
    { 
     if (Monitor.TryEnter(_workCheckLocker, 500)) 
     { 
      try 
      { 
       CheckForWork(); 
      } 
      catch (Exception ex) 
      { 
       Log.Error(EnvironmentName + " -- CheckForWork failed. " + ex); 
      } 
      finally 
      { 
       Monitor.Exit(_workCheckLocker); 
       Details.StillGettingWork = false; 
      } 
     } 
    } 
    else 
    { 
     Log.Standard("Continuing to get work."); 
    } 
} 

void CheckForWork() 
{ 
    Details.StillGettingWork = true; 
    //Hit web server to grab work. 
    //Log Processing 
    //Process Work 
} 

Теперь вот проблема:
Код выше позволяет 2 Таймер нити, чтобы получить в метод CheckForWork(). Я честно не понимаю, как это возможно, но я испытал это с несколькими клиентами, где работает это программное обеспечение.

Журналы, которые я получил сегодня, когда я нажал какую-то работу, показал, что он дважды проверял работу, и у меня было два потока, которые независимо пытались обработать, что приводило к сбою работы.

Processing 0-3978DF84-EB3E-47F4-8E78-E41E3BD0880E.xml for Update Request. - at 09/14 10:15:501255801 
Stopping environments for Update request - at 09/14 10:15:501255801 
Processing 0-3978DF84-EB3E-47F4-8E78-E41E3BD0880E.xml for Update Request. - at 09/14 10:15:501255801 
Unloaded AppDomain - at 09/14 10:15:10:15:501255801 
Stopping environments for Update request - at 09/14 10:15:501255801 
AppDomain is already unloaded - at 09/14 10:15:501255801 
=== Starting Update Process === - at 09/14 10:15:513756009 
Downloading File X - at 09/14 10:15:525631183 
Downloading File Y - at 09/14 10:15:525631183 
=== Starting Update Process === - at 09/14 10:15:525787359 
Downloading File X - at 09/14 10:15:525787359 
Downloading File Y - at 09/14 10:15:525787359 

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

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

Как может приведенный выше код действительно разрешить это? Я столкнулся с этой проблемой в прошлом году, когда у меня был lock вместо Monitor, и предположил, что это было только потому, что таймер в конечном итоге начал получать достаточно смещение из-за блокировки lock, что я получал таймерные потоки, штабелированные, т.е. один заблокирован на 5 секунд и пошел через как только Таймер запускал еще один обратный вызов, и оба они каким-то образом вошли в него. Вот почему я пошел с опцией Monitor.TryEnter, поэтому я бы не стал просто создавать потоки таймера стекирования.

Любой ключ? Во всех случаях, когда я пытался решить этот вопрос раньше, System.Threading.Timer был единственной константой, и я считаю ее основной причиной, но я не понимаю, почему.

+0

Любопытно, что 'Details.StillGettingWork' (или его поле поддержки) обозначено' volatile'? – itsme86

+0

@ itsme86 'Details' - это класс экземпляра, а' StillGettingWork' - авто-свойство. Ничто не отмечено изменчивым. – TyCobb

+0

Разве это не так, почему были созданы мьютексы? https://msdn.microsoft.com/en-us/library/windows/hardware/ff548097(v=vs.85).aspx –

ответ

0

TL; DR
Производственная хранимая процедура не обновлялась годами. Рабочие получали работу, которую они никогда не получали, и поэтому несколько работников обрабатывали запросы на обновление.


Я смог наконец найти время, чтобы правильно настроить себя локально, чтобы выступать в качестве производственного клиента через Visual Studio. Хотя, я не смог воспроизвести его, как я пережил, я случайно наткнулся на этот вопрос.

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

Оказалось, что в нашей производственной среде хранимая процедура для извлечения работы на основе типа работы не обновлялась годами (да, годами!) Развертываний. Все, что проверяло на работу, автоматически получало обновления, которые имели в виду, когда одновременно проверялись работники Update и Work Foo, оба они работали одинаково.

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

0

Я могу видеть в журнале, что вы указали, что у вас есть перезапуск AppDomain там, это правильно? Если да, уверены ли вы, что у вас есть и только один объект для вашей службы во время перезапуска AppDomain? Я думаю, что во время этого не все потоки останавливаются прямо в одно и то же время, и некоторые из них могут продолжить опрос рабочей очереди, поэтому два разных потока в разных AppDomain s имеют одинаковые Id для работы.

Вы, вероятно, могли бы это исправить с испачкать _workCheckLocker с static ключевое слово, например:

static object _workCheckLocker; 

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

Возможно, вы можете ввести словарь static вместо объекта для своих работников, поэтому вы можете проверить Id на наличие документов.

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

Кроме того, как сказал @fernando.reyes, вы можете ввести интенсивную структуру блокировки, называемую mutex для синхронизации, но это ухудшит вашу производительность.

+0

AppDomain используется для загрузки классов, которые выполняют фактическую обработку рабочего. Рабочий является общим. Когда он получает обновление, он в основном обновляет себя. Большое спасибо за ваше время. Мне просто нужно найти день, когда я смогу забить его и попытаться воспроизвести его через Visual Studio. – TyCobb

+0

О, хорошо. Я думаю, что код, который вы предоставили, является потокобезопасным. Может быть, по какой-то причине два разных сотрудника получили один и тот же файл для обработки. – VMAtm

+0

Они сделали .... = / – TyCobb

Смежные вопросы