这个缓存功能是否符合我的想法?

时间:2009-04-02 00:55:46

标签: c# .net caching weak-references

我暂时写了这个方法:

public static Func<T> WeakCacheFor<T>( Func<T> provider ) where T: class
{
    var cache = new WeakReference(null);
    return () => {
        var x = (T)cache.Target;
        if( x == null )
        {
            x = provider();
            cache.Target = x;
        }
        return x;
    };
}

所以有点背景知识:

我有一些冗长的传统方法,看起来有点像这样:

var id = GetDatabaseId();
if(a){
    var data = GetLoader().Init(id).GetData(); // expensive!
    // do stuff with data
}
if(b){
    // don't load data
}
... lots more variations, some contain GetLoader().Init(id).GetData(); some don't....

我的潜在解决方案是:

var id = GetDatabaseId();
var loadData = WeakCacheFor(() => GetLoader().Init(id).GetData());
if(a){
    var data = loadData();
    // do stuff with data
}
if(b){
    // don't load data
}
... lots more variations, some contain loadData(); some don't....

我对此的看法:

  • 我不需要缓存超出此方法调用的范围,因此如果GC在方法返回后立即收集它就没问题
  • 如果代码采用不需要加载数据的路径,则不会产生命中
  • 如果确实需要数据,如果再次需要,它将被缓存在弱引用中。
  • 如果GC确实在中途收集,那么无关紧要,因为它只会重新加载。

我的问题:

  1. 这实际上有用吗? - 我在WeakCacheFor方法中遗漏了哪些可能导致无意中被强烈引用的内容?
  2. 我是不是太聪明了? - 即使不需要,我是否应该发出命中并将数据缓存在正常的局部变量中?
  3. 我怀疑自己可能过于聪明,但即使我是这样,在其他任何人看来这样的解决方案是否可以在其他情况下有用?

    更新:修改了函数,因为apparently you can't trust .IsAlive

    更新:我意识到返回的Func将在方法结束时超出范围,因此我根本不需要弱参数,而正常的参考将正常工作。我觉得我遇到了“看不见森林为树木”的案例。

2 个答案:

答案 0 :(得分:5)

我没有看到使用弱引用的任何意义。一旦你加载了数据,几乎没有理由把它扔掉,直到你确定它不再有用为止。

您正在实施的是延迟加载模式的变体。坚持简单的模式,只需使用项目的常规参考:

public static Func<T> LazyLoad<T>(Func<T> provider) where T : class {
   T item = null;
   return () => {
      if (item == null) {
         item = provider();
      }
      return item;
   };
}

(还有一个小提示:你过分使用var关键字。)

答案 1 :(得分:2)

一般性意见:

  1. 您的解决方案(以及我的解决方案)不是线程安全的。
  2. 它不适用于IDisposable对象。
  3. 对于以下讨论,请考虑:

    foo = WeakCacheFor<Foo>(() => CreateFoo());
    

    案例#1:将foo用作长生命变量(例如长生命类的成员或全局变量)

    你的解决方案在这里。变量将在需要时创建,并在系统在GC期间释放资源时销毁。

    但请注意,如果foo时间昂贵但内存便宜,那么它可能会使用单例模式而是在应用程序的持续时间内加载一次?

    案例#2。使用foo作为局部变量。

    在这种情况下,我猜最好使用单例模式。考虑这样的例子:

    static void Main(string[] args)
    {
        MethodA(5, 7);
        MethodA(8, 9);
    }
    
    static void MethodA(int a, int b)
    {
        var foo = WeakCacheFor<Foo>(() => new Foo());
    
        if (a > 3)
        {
            Use(foo);
    
            if (a * b == 35)
            {
                GC.Collect(); // Simulate GC
                Use(foo);
            }
            else if(b % 6 == 2)
            {
                Use(foo);
            }
        }
    }
    

    foo将被创建3次。如果您对“模拟GC”行进行评论,则需要2次。你也不能将它用于IDisposable classess。

    现在让我们尝试一个单身人士:

    static void MethodA(int a, int b)
    {
        using (var foo = new MySingleton<Foo>(() => new Foo()))
        {
            if (a > 3)
            {
                Use(foo);
    
                if (a * b == 35)
                {
                    GC.Collect(); // Simulate GC
                    Use(foo);
                }
                else if (b % 6 == 2)
                {
                    Use(foo);
                }
            }
        }
    }
    

    正如您所看到的,代码几乎没有改变,但现在我们只有2次调用foo ctor和IDisposable支持。

    粗略的单例实现:

    class MySingleton<T> : IDisposable
        where T : class
    {
        private T _value;
        private Func<T> _provider;
    
        public MySingleton(Func<T> provider)
        {
            _provider = provider;
        }
    
        public T Get()
        {
            if (_value == null)
            {
                _value = _provider();
            }
    
            return _value;
        }
    
        #region IDisposable Members
    
        public void Dispose()
        {
            if(_value == null)
                return;
    
            IDisposable disposable = _value as IDisposable;
    
            if(disposable != null)
                disposable.Dispose();
        }
    
        #endregion
    }
    

    其余的代码:

    class Foo : IDisposable
    {
        public void Dispose() {}
    }
    
    static void Use(MySingleton<Foo> foo)
    {
        foo.Get();
    }
    
    static void Use(Func<Foo> foo)
    {
        foo();
    }