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

时间:2021-06-10 14:21:30

I've tentatively written this method:

我暂时写了这个方法:

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;
    };
}

So a bit of background:

所以有点背景:

I have some long-winded legacy methods which look a bit like this:

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

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....

My potential solution is to do this:

我的潜在解决方案是这样做:

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....

My thoughts about this:

我对此的想法:

  • I don't need to cache beyond the scope of this method call, so it's fine if the GC collects it as soon as the method returns
  • 我不需要缓存超出此方法调用的范围,因此如果GC在方法返回后立即收集它就没问题

  • If the code takes a path which doesn't need to load the data, the hit won't be incurred
  • 如果代码采用不需要加载数据的路径,则不会产生命中

  • If it does need the data it will be cached in the weak reference if it's needed again.
  • 如果确实需要数据,如果再次需要,它将被缓存在弱引用中。

  • If the GC does collect midway, it won't matter as it will just get re-loaded.
  • 如果GC确实在中途收集,那么它将无关紧要,因为它只会重新加载。

My questions:

  1. Will this actually work? - Is there anything that I've missed in the WeakCacheFor method that might cause a strong reference to inadvertenly be held?
  2. 这实际上有用吗? - WeakCacheFor方法中是否有任何我可能错过的内容可能导致无意中被强烈引用?

  3. Am I being too clever for my own good? - Should I just incur the hit and cache the data in a normal local variable even if it's not needed?
  4. 我是不是太聪明了? - 即使不需要,我是否应该发出命中并将数据缓存在正常的局部变量中?

I suspect I may be being too clever, but even if I am, does this seem to anyone else like a solution which can be usefully applied in other situations??

我怀疑我可能太聪明了,但即使我,这似乎是否有其他人喜欢在其他情况下有用的解决方案?

Update: Modified function because apparently you can't trust .IsAlive

更新:修改功能,因为显然你不能信任.IsAlive

Update: I realized that the returned Func will go out of scope at the end of the method, so I don't need a weakref at all and a normal ref will work just fine. I Was suffering from a case of "can't see the forest for the trees" I think.

更新:我意识到返回的Func将在方法结束时超出范围,所以我根本不需要weakref,而正常的ref将正常工作。我想,我正在遭遇“看不见森林为树木”的案例。

2 个解决方案

#1


I don't see any point in using a weak reference. Once you have loaded the data there is hardly any reason to throw it away until you are sure that it's not useful any more.

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

What you are implementing is a variation of the lazy loading pattern. Stick to the simple pattern and just use a regular reference to the item:

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

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

(And a small tip: You are using the var keyword way too much.)

(还有一个小提示:你使用var关键字太多了。)

#2


Couple of general comments:

几点评论:

  1. Your solution (as well as my) is not thread safe.
  2. 您的解决方案(以及我的)不是线程安全的。

  3. It is not designed to work with IDisposable objects.
  4. 它不适用于IDisposable对象。

For the following discussion consider:

以下讨论考虑:

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

Case # 1: Using foo as a long living variable (e.g. a member of the long living class, or a global variable)

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

Your solution makes sence here. The variable will be create when needed, and destroyed when system frees resources during the GC.

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

But notice, that if foo is time-expensive but memory-cheap, than probably it makes sence to use singleton pattern instead and load it once for the duration of the application?

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

Case # 2. Using foo as a local variable.

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

In this case it is better to use singleton pattern, I guess. Consider such example:

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

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);
        }
    }
}

The foo will be created 3 times. And 2 times if you comment the "Simulate GC" line. Also you can't use this for IDisposable classess.

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

Now let's try a singleton:

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

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);
            }
        }
    }
}

As you can see, the code almost did not change, but now we get only 2 calls to foo ctor and IDisposable support.

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

A rough singleton implementation:

粗略的单例实现:

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
}

And the rest of the code:

其余的代码:

class Foo : IDisposable
{
    public void Dispose() {}
}

static void Use(MySingleton<Foo> foo)
{
    foo.Get();
}

static void Use(Func<Foo> foo)
{
    foo();
}

#1


I don't see any point in using a weak reference. Once you have loaded the data there is hardly any reason to throw it away until you are sure that it's not useful any more.

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

What you are implementing is a variation of the lazy loading pattern. Stick to the simple pattern and just use a regular reference to the item:

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

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

(And a small tip: You are using the var keyword way too much.)

(还有一个小提示:你使用var关键字太多了。)

#2


Couple of general comments:

几点评论:

  1. Your solution (as well as my) is not thread safe.
  2. 您的解决方案(以及我的)不是线程安全的。

  3. It is not designed to work with IDisposable objects.
  4. 它不适用于IDisposable对象。

For the following discussion consider:

以下讨论考虑:

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

Case # 1: Using foo as a long living variable (e.g. a member of the long living class, or a global variable)

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

Your solution makes sence here. The variable will be create when needed, and destroyed when system frees resources during the GC.

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

But notice, that if foo is time-expensive but memory-cheap, than probably it makes sence to use singleton pattern instead and load it once for the duration of the application?

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

Case # 2. Using foo as a local variable.

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

In this case it is better to use singleton pattern, I guess. Consider such example:

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

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);
        }
    }
}

The foo will be created 3 times. And 2 times if you comment the "Simulate GC" line. Also you can't use this for IDisposable classess.

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

Now let's try a singleton:

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

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);
            }
        }
    }
}

As you can see, the code almost did not change, but now we get only 2 calls to foo ctor and IDisposable support.

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

A rough singleton implementation:

粗略的单例实现:

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
}

And the rest of the code:

其余的代码:

class Foo : IDisposable
{
    public void Dispose() {}
}

static void Use(MySingleton<Foo> foo)
{
    foo.Get();
}

static void Use(Func<Foo> foo)
{
    foo();
}