首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用于缓存辅助的自定义字典

用于缓存辅助的自定义字典
EN

Code Review用户
提问于 2012-12-08 21:28:06
回答 1查看 1.1K关注 0票数 4

我正在编写一个自定义字典,它将用作缓存的助手。我使用委托的原因是因为它必须与生成的代码接口,所以我不能更改它。但是,您可以假设委托是线程安全的。我做了一些初步的测试,它是线程安全的,但我总是可以使用额外的一对眼睛来发现可能的并发问题。

代码语言:javascript
复制
//StoreToCache will handle removal, replacement, and addition to the cache
public delegate object StoreToCache(string key, object value, CacheObject info);
public delegate object GetFromCache(string key);

/// <summary>
/// A super fast concurrent "dictionary" which passes through to the generated Cache class which constructed it. 
/// </summary>
public class CacheDictionary<K,V>
{
    ConcurrentDictionary<K, string> RealKeys=new ConcurrentDictionary<K, string>();
    readonly string BaseKey;
    readonly CacheObject Info;
    StoreToCache StoreTo;
    GetFromCache GetFrom;
    /// <summary>
    /// This returns the amount of keys we are tracking within this CacheDictionary.
    /// Note: This does not necessarily indicate how many items are actually still in the cache! 
    /// </summary>
    /// <returns>
    /// The count.
    /// </returns>
    public int TrackedCount
    {
        get
        {
            return RealKeys.Count;
        }
    }
    public CacheDictionary(string basekey, CacheObject info, StoreToCache store, GetFromCache get)
    {
        BaseKey=basekey;
        Info=info;
        StoreTo=store;
        GetFrom=get;
    }

    void Add (K key, V value)
    {
        string realkey=RealKeys.GetOrAdd(key, (s) => AddKey(key));
        StoreTo(realkey, value, Info);
    }
    public V Remove (K key)
    {
        var res=StoreTo(GetKey(key), null, Info);
        string trash=null;
        RealKeys.TryRemove(key, out trash);
        if(res!=null && res is V)
        {
            return (V)res;
        }
        else
        {
            return default(V);
        }
    }
    static long CurrentKey=0;
    string AddKey(K key)
    {
        long tmp=Interlocked.Increment(ref CurrentKey);
        string k=BaseKey+(tmp).ToString();
        if(!RealKeys.TryAdd(key, k))
        {
            return null;
        }
        return k;
    }
    string GetKey(K key)
    {
        string tmp=null;
        if(!RealKeys.TryGetValue(key, out tmp))
        {
            return null;
        }
        return tmp;
    }
    public V this [K key] {
        get {
            string realkey=GetKey(key);
            if(realkey==null)
            {
                return default(V);
            }
            object tmp=GetFrom(realkey);
            if(tmp!=null && tmp is V)
            {
                return (V)tmp;
            }
            else
            {
                string trash=null;
                RealKeys.TryRemove(key, out trash); //cleanup
                return default(V);
            }
        }
        set {
            if(value==null)
            {
                Remove(key);
            }
            else
            {
                Add (key, value);
            }
        }
    }
    public void Clear ()
    {
        lock(RealKeys)
        {
            foreach(var key in RealKeys.Keys)
            {
                //don't worry about concurrency here. Iterating over the collection is so non-thread-safe it's not even funny. 
                StoreTo(GetKey(key), null, Info);
            }
            RealKeys.Clear();
        }
    }
}

这个代码线程安全吗?还假设委托可以返回基本上随机的值,因为它们传递到缓存。因此,它们可能在任何时候都具有所要求的价值,也可能没有。如果委托没有请求的值,它将返回null

这个字典总是被初始化为静态的,并且基本上可以同时从无限数量的线程中访问。它不应该抛出任何一种例外。如果一个值不存在,它应该返回null

这个线程在任何情况下都安全吗?另外,有什么东西可以使代码更加清晰吗?

用例如下所示:

代码语言:javascript
复制
static ConcurrentDictionary<int, string> d=new ConcurrencyDictionary....
....
d[10]="foo"; //add a value if it doesn't exist
d[10]=null; //remove value if it exists
string tmp=d[1]; //read value. returns the value if it exists, else null
EN

回答 1

Code Review用户

发布于 2012-12-09 14:30:46

您的代码不安全:

  • 同时调用索引器和setter的相同键可能导致缓存中缺少数据:
    • setter将键添加到RealKeys
    • getter读取它并在缓存中寻找值,而它还没有出现。
    • setter将值添加到缓存中。
    • getter清理缓存。

  • 锁定Clear并不会阻止其他方法更新RealKeys,因为其他方法不会锁定RealKeys

其他有代码值得注意的问题:

  • 命名惯例。私有字段通常在camelCase中命名,并且通常带有下划线前缀。
  • 一项责任。CacheDictionary不需要了解CacheObject。相反,如果“真正的缓存”实现了一个接口,并且它是作为缓存实现而不是两个委托传递的,那就更好了。
  • 这个缓存将映射键->真实键存储在内存中,因此真正的缓存很可能也在内存中。只使用ConcurrentDictionary<TKey, TValue>System.Runtime.Caching.MemoryCache可能是个好主意。
票数 7
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/19411

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档