【发布时间】:2014-11-21 22:33:55
【问题描述】:
我正在尝试控制对对象的访问,以便在给定的时间跨度内只能访问一定次数。在我拥有的一个单元测试中,访问限制为每秒一次。所以 5 次访问应该只需要 4 秒多一点。但是,我们的 TFS 服务器上的测试失败了,只用了 2 秒。我的代码的精简版本在这里:
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));
}
我想知道测试中的循环是否正在以某种方式进行优化,即它在单独的线程(或类似的东西)上运行循环的每次迭代,这意味着测试完成得比它应该更快,因为只有 Thread.Sleep阻塞正在调用它的线程?
【问题讨论】:
-
代码有一些奇怪的地方——比如,我不明白为什么你有
_lastUse,最好使用Stopwatch而不是DateTime.UtcNow来跟踪时间 - 但我没有看到任何可以解释问题的明显错误。唯一想到的是(因为您没有使用Stopwatch),如果系统时间由于某种原因未能更新,时钟时间将错误地测量实际经过的时间。这极不太可能与编译器优化有关,尤其是涉及线程的优化。 -
问问自己当
_lastReset.Add(_rateLimitSpan) == DateTime.UtcNow时会发生什么。此外,对于这个写得很好的代码,依赖_lastReset = default(DateTime)初始化是奇怪和不透明的。此外,真正的代码不应该Sleep()。考虑等待 Waithandle 或其他东西。 -
此测试失败的一种不太可能但理论上绝对可能的方式是,如果在您运行测试时与时间服务器同步(并且您无法控制)。不过,如果这确实是问题所在,我会感到非常惊讶。
-
发生了一件非常奇怪的事情——我对同一个项目的另一部分进行了更改,提交了代码,并且所有测试都通过了。
标签: c# multithreading loops compiler-optimization