实施并发词典

时间:2018-11-21 15:55:20

标签: c# multithreading thread-safety concurrentdictionary

我正在尝试为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;
}

2 个答案:

答案 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(onClearedGetOrAdd)对状态进行原子更改。您的情况不满足第二个要求,因为您需要根据其值的状态remove keys conditionally,并且AddOrUpdate类不支持此方案。

因此您当前的缓存实现不是线程安全的。偶尔抛出异常的事实是一个巧合。如果它是完全防丢的,它将仍然是非线程安全的,因为它不是完全防错的,这意味着它可能偶尔(或永久地)过渡到与其规范不兼容的状态(例如返回过期值) )。

关于ConcurrentDictionary类,如果您在一台计算机上对该类进行了广泛的测试,则该类会遭受可见性错误的影响,而该可见性错误可能会一直被忽略,而当您将应用程序部署到另一台具有不同CPU体系结构的计算机中时,该缺陷会突然出现。非volatile ThrottleInfo字段是通过公共属性private int _requestCount公开的,因此不能保证(基于C#规范)所有线程都将看到其最新值。您可以阅读Igor Ostrovsky的this article,了解内存模型的特殊性,这可能使您(像我一样)相信与多线程代码一起使用无锁技术(在这种情况下使用RequestCount类)麻烦多于其价值。如果您阅读并喜欢它,那么本文还会有part 2

相关问题