2014-11-21 6 views
1

개체에 대한 액세스를 제어하여 주어진 timespan 동안 특정 횟수에만 액세스 할 수 있습니다. 내가 가지고있는 단위 테스트에서 액세스는 초당 한 번으로 제한됩니다. 따라서 5 회의 액세스는 4 초 이상 걸릴 것입니다. 그러나 TFS 서버에서는 2 초 만에 테스트가 실패합니다. 이 작업을 수행하려면 코드의 버전을 박탈은 여기에 있습니다 :C# 컴파일러 최적화 루프?

public class RateLimitedSessionStrippedDown<T> 
{ 
    private readonly int _rateLimit; 
    private readonly TimeSpan _rateLimitSpan; 
    private readonly T _instance; 
    private readonly object _lock; 

    private DateTime _lastReset; 
    private DateTime _lastUse; 
    private int _retrievalsSinceLastReset; 

    public RateLimitedSessionStrippedDown(int limitAmount, TimeSpan limitSpan, T instance) 
    { 
     _rateLimit = limitAmount; 
     _rateLimitSpan = limitSpan; 
     _lastUse = DateTime.UtcNow; 
     _instance = instance; 
     _lock = new object(); 
    } 

    private void IncreaseRetrievalCount() 
    { 
     _retrievalsSinceLastReset++; 
    } 

    public T GetRateLimitedSession() 
    { 
     lock (_lock) 
     { 
      _lastUse = DateTime.UtcNow; 

      Block(); 

      IncreaseRetrievalCount(); 

      return _instance; 
     } 
    } 

    private void Block() 
    { 
     while (_retrievalsSinceLastReset >= _rateLimit && 
      _lastReset.Add(_rateLimitSpan) > DateTime.UtcNow) 
     { 
      Thread.Sleep(TimeSpan.FromMilliseconds(10)); 
     } 

     if (DateTime.UtcNow > _lastReset.Add(_rateLimitSpan)) 
     { 
      _lastReset = DateTime.UtcNow; 
      _retrievalsSinceLastReset = 0; 
     } 
    } 
} 

디버그 및 릴리스 모두에서 잘 작동, 내 컴퓨터에서 실행하는 동안. 그러나 TFS 빌드 서버에 커밋하면 유닛 테스트가 실패합니다. 이것은 테스트입니다 : 시험의 루프는 별도의 스레드 (또는 뭔가 유사한)에 루프의 각 반복을 실행하는 방식으로 최적화되고 있는지 궁금

[Test] 
    public void TestRateLimitOnePerSecond_AssertTakesAtLeastNMinusOneSeconds() 
    { 
     var rateLimiter = new RateLimitedSessionStrippedDown<object>(1, TimeSpan.FromSeconds(1), new object()); 

     DateTime start = DateTime.UtcNow; 

     for (int i = 0; i < 5; i++) 
     { 
      rateLimiter.GetRateLimitedSession(); 
     } 

     DateTime end = DateTime.UtcNow; 

     Assert.GreaterOrEqual(end.Subtract(start), TimeSpan.FromSeconds(4)); 
    } 

Test failure from the TFS CI Build Server

하는 Thread.Sleep은 호출되는 스레드 만 차단하기 때문에 테스트가 더 빨리 완료되어야 함을 의미합니다.

+0

코드에 이상한 점이 있습니다 (예 :'_lastUse'가있는 이유를 알 수 없으며'DateTime.UtcNow' 대신'Stopwatch'를 사용하여 시간을 추적하는 것이 더 낫습니다) 그러나 나는 그 문제를 설명 할 명백한 어떤 것도 잘못 보이지 않습니다. 유일한 이유는 ('Stopwatch'를 사용하지 않으므로) 어떤 이유로 시스템 시간이 업데이트되지 않으면 시계 시간이 실제 경과 시간을 잘못 측정한다는 것입니다. 이것은 대개 컴파일러 최적화, 특히 스레딩과 관련이 있습니다. –

+0

'_lastReset.Add (_rateLimitSpan) == DateTime.UtcNow' 때 어떤 일이 일어나는지 스스로에게 물어보십시오. 또한, 잘 작성된이 코드의 경우'_lastReset = default (DateTime)'초기화에 의존하는 것이 궁금하고 불투명하다. 또한 실제 코드는'Sleep()'해서는 안됩니다.Waithandle 등을 기다리는 것을 고려하십시오. –

+0

이론적으로는 이론적으로 가능한이 테스트가 실패 할 수있는 방법은 테스트를 실행하는 동안 시간 서버와의 동기화가 이루어지고 (그리고 제어 할 수없는 경우) 비록 이것이 실제로 문제가된다면 그것은 나에게 매우 놀랄 것입니다. – hvd

답변

3

당신의 문제는 Block 방법 안에 있으며, 이제 나는 주석을 보았습니다. Henk Holterman이 이미이 문제를 제기 한 것으로 보입니다.

_lastReset.Add(_rateLimitSpan)DateTime.UtcNow이 같은 경우에만 오류가 발생합니다. 이것은 자주 일어나지 않으므로 간헐적으로 실패하는 이유입니다. 수정 사항이 줄에 >=>을 변경하는 것입니다 :

if (DateTime.UtcNow > _lastReset.Add(_rateLimitSpan)) 

그것은하지 직관적 왜, 당신이 DateTime.UtcNow 호출 할 때마다 반드시 새로운 값 하나 각각의 호출을 반환하지 않는 것을 이해하지.

DateTime.UtcNow은 정확히 100 나노초까지 정확하지만 정밀도는 정확도와 다릅니다. 그것은 컴퓨터의 타이머 간격에 따라 달라 지지만 1-15ms이지만 더 자주 15.25ms로 설정됩니다. 멀티미디어를 사용하지 않는 한.

이 동작은 dotnetfiddle에서 볼 수 있습니다. 1ms와 같이 타이머를 다른 값으로 설정 한 프로그램이 열려 있지 않으면 틱 간의 차이가 약 150000 ticks, 약 15ms 또는 일반적인 시스템 타이머 간격이라는 것을 알 수 있습니다.

우리는 또한 임시 변수에 DateTime.UtcNow에 전화를 리프팅 및 방법의 끝에 그들을 비교하여 유효성을 검사 할 수 있습니다 : 내 컴퓨터에

private void Block() 
    { 
     var first = DateTime.UtcNow; 
     while (_retrievalsSinceLastReset >= _rateLimit && 
      _lastReset.Add(_rateLimitSpan) > first) 
     { 
      Thread.Sleep(TimeSpan.FromMilliseconds(10)); 
      first = DateTime.UtcNow; 
     } 

     var second = DateTime.UtcNow; 
     if (second > _lastReset.Add(_rateLimitSpan)) 
     { 
      _lastReset = DateTime.UtcNow; 
      _retrievalsSinceLastReset = 0; 
     } 

     if (first == second) 
     { 
      Console.WriteLine("DateTime.UtcNow returned same value"); 
     } 
    } 

, Block에 다섯 호출 것으로 DateTime.UtcNow을 인쇄 같은.