У меня есть служба 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
был единственной константой, и я считаю ее основной причиной, но я не понимаю, почему.
Любопытно, что 'Details.StillGettingWork' (или его поле поддержки) обозначено' volatile'? – itsme86
@ itsme86 'Details' - это класс экземпляра, а' StillGettingWork' - авто-свойство. Ничто не отмечено изменчивым. – TyCobb
Разве это не так, почему были созданы мьютексы? https://msdn.microsoft.com/en-us/library/windows/hardware/ff548097(v=vs.85).aspx –