我正在尝试为API创建自己的缓存实现。这是我第一次使用ConcurrentDictionary,但我不知道我是否正确使用它。在测试中,某些东西抛出了错误,到目前为止,我还无法重现它。也许一些并发专家/ ConcurrentDictionary可以查看代码并查找可能出问题的地方。谢谢!
private static readonly ConcurrentDictionary<string, ThrottleInfo> CacheList = new ConcurrentDictionary<string, ThrottleInfo>();
public override void OnActionExecuting(HttpActionContext actionExecutingContext)
{
if (CacheList.TryGetValue(userIdentifier, out var throttleInfo))
{
if (DateTime.Now >= throttleInfo.ExpiresOn)
{
if (CacheList.TryRemove(userIdentifier, out _))
{
//TODO:
}
}
else
{
if (throttleInfo.RequestCount >= defaultMaxRequest)
{
actionExecutingContext.Response = ResponseMessageExtension.TooManyRequestHttpResponseMessage();
}
else
{
throttleInfo.Increment();
}
}
}
else
{
if (CacheList.TryAdd(userIdentifier, new ThrottleInfo(Seconds)))
{
//TODO:
}
}
}
public class ThrottleInfo
{
private int _requestCount;
public int RequestCount => _requestCount;
public ThrottleInfo(int addSeconds)
{
Interlocked.Increment(ref _requestCount);
ExpiresOn = ExpiresOn.AddSeconds(addSeconds);
}
public void Increment()
{
// this is about as thread safe as you can get.
// From MSDN: Increments a specified variable and stores the result, as an atomic operation.
Interlocked.Increment(ref _requestCount);
// you can return the result of Increment if you want the new value,
//but DO NOT set the counter to the result :[i.e. counter = Interlocked.Increment(ref counter);] This will break the atomicity.
}
public DateTime ExpiresOn { get; } = DateTime.Now;
}
答案 0 :(得分:0)
如果我了解在ExpiresOn通过之后您要做什么,请删除该条目,否则对其进行更新或添加(如果不存在)。 您当然可以利用AddOrUpdateMethod简化一些代码。 看看这里有一些很好的例子:https://docs.microsoft.com/en-us/dotnet/standard/collections/thread-safe/how-to-add-and-remove-items 希望这会有所帮助。
答案 1 :(得分:0)
ConcurrentDictionary
仅在以下情况下才是线程安全的容器:(1)需要保护的整个状态是其内部状态(它所包含的键和值),并且在以下情况下(2)可以使用其提供的专用API(onCleared
,GetOrAdd
)对状态进行原子更改。您的情况不满足第二个要求,因为您需要根据其值的状态remove keys conditionally,并且AddOrUpdate
类不支持此方案。
因此您当前的缓存实现不是线程安全的。偶尔抛出异常的事实是一个巧合。如果它是完全防丢的,它将仍然是非线程安全的,因为它不是完全防错的,这意味着它可能偶尔(或永久地)过渡到与其规范不兼容的状态(例如返回过期值) )。
关于ConcurrentDictionary
类,如果您在一台计算机上对该类进行了广泛的测试,则该类会遭受可见性错误的影响,而该可见性错误可能会一直被忽略,而当您将应用程序部署到另一台具有不同CPU体系结构的计算机中时,该缺陷会突然出现。非volatile ThrottleInfo
字段是通过公共属性private int _requestCount
公开的,因此不能保证(基于C#规范)所有线程都将看到其最新值。您可以阅读Igor Ostrovsky的this article,了解内存模型的特殊性,这可能使您(像我一样)相信与多线程代码一起使用无锁技术(在这种情况下使用RequestCount
类)麻烦多于其价值。如果您阅读并喜欢它,那么本文还会有part 2。