我试图控制对一个对象的访问,以便它可能只在给定的时间范围内被访问一定次数。在我有一个单元测试中,访问限制为每秒一次。所以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;
}
}
}
在我的电脑上运行时,在Debug和Release中,它都能正常工作。但是,我有一个单元测试失败,一旦我提交到我们的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));
}
我不知道如果在测试循环中,它运行在一个单独的线程(或类似的东西),循环的每次迭代的方式不断优化其意味着测试比它应该更快地完成,因为Thread.Sleep只会阻止它被调用的线程?
关于代码有一些奇怪的地方 - 比如,我不明白为什么你有'_lastUse',并且最好使用'Stopwatch'而不是'DateTime.UtcNow'来跟踪时间 - 但我没有看到任何可以解释问题的明显错误。唯一想到的是可能性(因为你没有使用'Stopwatch'),如果由于某种原因系统时间无法更新,时钟时间将错误地测量实际的经过时间。这很不可能与编译器优化有关,特别是涉及线程的优化。 – 2014-11-21 22:57:20
问自己当_lastReset.Add(_rateLimitSpan)== DateTime.UtcNow'时会发生什么。另外,对于这个写得很好的代码,依赖'_lastReset = default(DateTime)'初始化是很好奇和不透明的。此外,真正的代码不应该'睡眠()'。考虑等待Waithandle或其他东西。 – 2014-11-21 23:06:08
这个测试失败的可能性虽然不大但在理论上可行,但如果在运行测试的同时发生与时间服务器的同步(并且您无法控制该测试),那么测试失败的方式是可行的。但是,如果这实际上是问题,那将是非常令人惊讶的。 – hvd 2014-11-22 00:20:29