2012-11-21 5 views
3

검색을 시도했지만 내가 직면 한 문제에 가장 적합한 제안을 찾지 못했습니다.여러 리소스에 대한 Monitor.TryEnter

제 문제는 사용 가능한 리소스 (계산 엔진)의 목록/스택이 있다는 것입니다. 이러한 자원은 특정 계산을 수행하는 데 사용됩니다.

계산을 수행하라는 요청은 외부 프로세스에서 트리거됩니다. 따라서 계산 요청이있을 때 사용 가능한 리소스가 현재 다른 계산을 수행하지 않는지 확인해야합니다. 잠시 기다렸다가 다시 확인하십시오.

저는 이것을 구현하는 가장 좋은 방법이 무엇인지 궁금합니다. 다음 코드를 제 위치에두고 있지만 매우 안전한지 확실하지 않습니다. 당신이 더 제안이 있다면

, 즉 큰 것 :

void Process(int retries = 0) { 
    CalcEngineConnection connection = null; 
    bool securedConnection = false; 
    foreach (var calcEngineConnection in _connections) { 
     securedConnection = Monitor.TryEnter(calcEngineConnection); 
     if (securedConnection) { 
      connection = calcEngineConnection; 
      break; 
     } 
    } 
    if (securedConnection) { 
     //Dequeue the next request 
     var calcEnginePool = _pendingPool.Dequeue(); 

     //Perform the operation and exit. 
     connection.RunCalc(calcEnginePool); 
     Monitor.Exit(connection); 
    } 
    else { 
     if (retries < 10) 
      retries += 1; 
     Thread.Sleep(200); 
     Process(retries); 
    } 
} 

답변

2

내가 Monitor를 사용하여 여기 어쨌든 가장 좋은 방법임을 잘 모르겠지만, 그 경로를 이동하기로 결정 당신이 경우,

    : 이것은 논리의 세 가지를 분해

    bool TryProcessWithRetries(int retries) { 
        for (int attempt = 0; attempt < retries; attempt++) { 
         if (TryProcess()) { 
          return true; 
         } 
         Thread.Sleep(200); 
        } 
        // Throw an exception here instead? 
        return false; 
    } 
    
    bool TryProcess() { 
        foreach (var connection in _connections) { 
         if (TryProcess(connection)) { 
          return true; 
         } 
        } 
        return false; 
    } 
    
    bool TryProcess(CalcEngineConnection connection) { 
        if (!Monitor.TryEnter(connection)) { 
         return false; 
        } 
        try { 
         var calcEnginePool = _pendingPool.Dequeue(); 
         connection.RunCalc(calcEnginePool); 
        } finally { 
         Monitor.Exit(connection); 
        } 
        return true; 
    } 
    

    : 나는에 위의 코드를 리팩토링 것

  • 다시 시도를 여러 번
  • 단일 연결을 시도 모음
  • 의 각 연결을 시도

또한 그것의 위해 재귀를 사용 방지 및 finally 블록에 Monitor.Exit 통화를 보류하는 그것을 절대적으로는에 있어야

당신이 중간 메소드 구현을 대체 할 수있다 :.

return _connections.Any(TryProcess); 

...하지만 그 자체가 좋기 때문에 조금 "똑똑한"것일 수도 있습니다. 코드는 연결이 뭔가를 처리 할 수 ​​있는지 여부에 대해 알 필요가 없다 그런 식으로 - -이 객체 자체에 달려

개인적으로 나는 CalcEngineConnection 자체에 TryProcess을 이동 유혹 할 것입니다. 이는 공개적으로 볼 수있는 잠금을 피할 수 있음을 의미하며, 일부 리소스가 향후 한 번에 두 개의 요청을 처리 할 수있는 경우 유연합니다.

+0

감사합니다. Jon, Monitor를 사용하지 않을 경우 궁금한 점이 무엇입니까? –

+0

정확한 시나리오를 알려주기 위해 특정 계산을 수행하는 연결 목록이 있으며 여러 소스에서 계산 요청 (어쩌면 동시에)이 표시됩니다. 제 의도는 이러한 요청 (_pendingPool)을 대기열에 넣고 계산이 트리거되는 즉시 사용 가능한 자원이 있는지 확인하고 각 요청을 대기열에서 제외하고 계산을 수행하는 것입니다. –

+1

@UmeshNavani : 대기열에서 * 푸시 *하는 대신 연결에서 끌어낼 수있는 것처럼 들리 - 기본적으로 여러 소비자가있는 생산자/고객 대기열이 있습니다. .NET 4를 사용한다면,이 측면을 위해 설계된'BlockingCollection'을보십시오. 덕분에 –

1

가 잠재적으로 발생할 수있는 여러 문제지만의 처음 코드를 단순화하자

void Process(int retries = 0) 
{ 
    foreach (var connection in _connections) 
    { 
     if(Monitor.TryEnter(connection)) 
     { 
      try 
      { 
       //Dequeue the next request 
       var calcEnginePool = _pendingPool.Dequeue(); 

       //Perform the operation and exit. 
       connection.RunCalc(calcEnginePool); 
      } 
      finally 
      { 
       // Release the lock 
       Monitor.Exit(connection); 
      } 
      return; 
     } 
    } 

    if (retries < 10) 
    { 
     Thread.Sleep(200); 
     Process(retries+1); 
    } 
} 

이 올바르게 연결을 보호하지만, 여기에 가정 중 하나가 당신의 _connections 목록이 안전하다는 것을 유의하고 다른 스레드가 수정하지 않습니다.

_connections의 경우 스레드 안전 큐를 사용하는 것이 좋습니다. 특정로드 레벨에서는 처음 몇 개의 연결 만 사용하게 될 수 있습니다 (차이가 있는지 여부는 확실하지 않음). 상대적으로 균등하게 모든 연결을 사용하려면 큐에 넣고 큐에서 대기시킵니다.또한 두 스레드가 동일한 연결을 사용하고 있지 않으므로 Monitor.TryEnter()을 사용할 필요가 없습니다.

+0

감사합니다. Lirik, 답변 및 Jons 답변이 정말 유용했습니다. 귀하의 우려 사항 중 하나는 _connections에 대한 스레드 안전 큐가 필요하지 않습니다.이 목록은 시작시 정적 구성 파일에서 채워 지므로 언제든지 변경되지 않습니다. –

+0

@UmeshNavani 스레드 안전 큐의 이점은'Monitor.TryEnter'를 없앨 수 있다는 것입니다. 처음에는 어떻게 채워야하는지는 중요하지 않습니다. Jon은 이미 스레드 세이프 큐에 매우 근접한 BlockingCollection을 추천했다. – Kiril

관련 문제