2016-09-15 5 views
6

5 초마다 작업을 확인하는 Windows 서비스가 있습니다. 검사 및 처리를 처리하는 데 System.Threading.Timer을 사용하고 Monitor.TryEnter을 사용하여 하나의 스레드 만 작업을 확인하는지 확인합니다.Monitor.TryEnter 및 Threading.Timer 경쟁 조건

다음 코드는 서비스에 의해 작성된 8 명의 다른 근로자의 일부이며 각 작업자는 확인해야하는 고유 한 특정 유형의 작업을 가지고 있으므로이 방법으로 가정해야합니다.
위의 코드는 2 개 타이머 스레드가 CheckForWork() 방법으로 얻을 수있게된다

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 
} 

지금 여기서 문제입니다. 나는 솔직히 이것이 어떻게 가능한지 이해하지 못한다. 그러나 나는이 소프트웨어가 실행되는 여러 클라이언트에서 이것을 경험했다.

내가 오늘 작업 한 일부 로그를 보았을 때 작업을 두 번 확인했는데 독립적으로 처리하려고했던 두 개의 스레드가 있었는데 작업이 실패하여 계속 실패했습니다.

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 스레드는 파일에서 액세스가 거부되고 전체 업데이트가 실패하는 큰 파일을 다운로드하기 시작합니다.

어떻게 위 코드가 실제로 이것을 허용합니까? 나는 lockMonitor 대신 가지고 있었고 타이머가 결국 lock 블로킹으로 인해 충분히 오프셋되기 시작했기 때문에 지난해에이 문제를 경험했습니다. 타이머가 다른 콜백을 트리거하고있는 것처럼 오른쪽을 통해 그들은 모두 어떻게 든 그것을 만들었습니다. 그래서 저는 Monitor.TryEnter 옵션을 사용하여 타이머 스태킹을 계속 스태킹하지 않을 것입니다.

단서가 있습니까? 이전에이 문제를 해결하기 위해 노력한 모든 경우에 System.Threading.Timer은 하나의 상수였으며 그 근본 원인이라고 생각합니다.하지만 이유를 이해하지 못합니다.

+0

'Details.StillGettingWork' (또는 그 뒷받침 필드)가'휘발성 '으로 표시됩니까? – itsme86

+0

@ itsme86'Details'는 인스턴스 클래스이고'StillGettingWork'는 자동 속성입니다. 휘발성으로 표시된 것은 없습니다. – TyCobb

+0

뮤텍스가 만들어진 이유는 무엇입니까? https://msdn.microsoft.com/en-us/library/windows/hardware/ff548097(v=vs.85).aspx –

답변

0

TL
생산 저장 프로 시저가 수년간 업데이트되지 않았습니다. 노동자들은 결코 얻지 못했던 일을하고 있었고 그래서 여러 명의 노동자들이 업데이트 요청을 처리하고있었습니다.


Visual Studio를 통해 프로덕션 클라이언트로 작동하도록 로컬 시간을 올바르게 설정할 수있는 시간을 마침내 찾을 수있었습니다. 경험 한 것처럼 재현 할 수 없었지만 실수로 우연히 발견되었습니다.

여러 작업자가 작업을 선택했다는 가정은 참으로 정확했으며 이는 각 작업자가 수행하고 요청한 작업에서 고유하므로 일어날 수 없었어야합니다.

프로덕션 환경에서 작업 유형에 따라 작업을 검색하는 저장 프로 시저가 배포의 몇 년 (예, 몇 년!)에 업데이트되지 않은 것으로 나타났습니다. 작업을 확인한 모든 항목에 자동으로 업데이트가 발생 했으므로 업데이트 작업자와 작업자 Foo가 동시에 확인한 경우 모두 동일한 작업으로 끝났습니다.

다행히도이 수정 프로그램은 데이터베이스 측이며 클라이언트 업데이트가 아닙니다.

0

당신이 제공 한 로그에서 AppDomain을 다시 시작한 것을 볼 수 있습니다. 맞습니까? 예인 경우 AppDomain을 다시 시작하는 동안 중 하나만 사용할 수 있습니까? 나는 그 동안 모든 스레드가 똑같은 시간에 멈추지 않고 그 중 일부는 작업 대기열을 폴링하는 것으로 진행할 수 있다고 생각하므로 다른 AppDomain에있는 두 개의 다른 스레드는 동일한 Id을 가지고 있습니다.

static object _workCheckLocker; 

을 당신이 직면 할 수 인라인 초기화의 경우에는이 필드 (초기화하여 클래스의 정적 생성자를 소개 :

당신은 아마 이런 식으로, 당신의 _workCheckLockerstatic와 키워드를 표시하여이 문제를 해결할 수 몇 가지 더 복잡한 문제),하지만 이건 당신의 사건에 대한 충분하지 모르겠어요 - AppDomain 다시 정적 클래스를 다시로드합니다.내가 아는 한, 이것은 당신을위한 선택 사항이 아닙니다.

직원 대신 개체 대신 static 사전을 도입 할 수 있으므로 진행중인 문서의 Id을 확인할 수 있습니다.

또 다른 방법은 아마 당신이 CancellationToken을 소개하는 AppDomain 다시 시작할 때 호출 할 수있는 서비스에 대한 Stopping 이벤트를 처리하고, 이러한 상황시 모든 작업을 중지하는 데 사용하는 것입니다.

또한 @ fernando.reyes가 말한 것처럼 동기화를 위해 뮤텍스라고하는 무거운 잠금 구조를 도입 할 수 있지만 이는 성능을 저하시킵니다.

+0

AppDomain은 작업자가 필요로하는 실제 처리를 수행하는 클래스를로드하는 데 사용됩니다. 작업자는 일반적입니다. 업데이트를 받으면 기본적으로 업데이트됩니다. 시간 내 주셔서 감사합니다. 나는 그것을 망칠 수 있고 Visual Studio를 통해 재현하려고하는 하루를 찾아야 할 것입니다. – TyCobb

+0

오, 오케이. 나는 당신이 제공 한 코드가 thread-safe하다고 생각한다. 어쩌면 어떤 이유로 두 명의 다른 직원이 동일한 파일을 처리 할 수 ​​있습니다. – VMAtm

+0

그들은 했어 .... = / – TyCobb