方法的缓存属性?

21

也许这只是梦想,但是否可能创建一个属性来缓存函数的输出(如 HttpRuntime.Cache),并在函数的参数相同时返回缓存中的值,而不是实际执行函数?

当我说“函数”时,我指的是任何函数,无论它是从数据库获取数据,还是将两个整数相加,或者是输出文件的内容。任何函数。


或许你在说模拟(mocking)? - Felice Pollano
我想知道使用dynamic实现这个功能是否可行。创建一个包装器,接受动态调用,调用实际对象并缓存结果。 - Matt Greer
@Matt Greer,你不需要使用动态语言来实现这样的功能,因为你已经同意放弃使用AOP。我创建了一个基类Cacheable<T>,你只需要继承它,定义一个缓存区域并给它一个lambda表达式,在缓存未命中时如何加载缓存即可。 - Chris Marisic
@Chris Marisic -- 你介意分享你的 Cacheable<T> 类吗?我想看看它。 - Matt Greer
@PostgresQLNewb:我用今天找到的新链接更新了我的旧答案,以防你仍然对这个东西感兴趣。 - Dyppl
6个回答

14

2
这段代码很棒,我刚刚下载并在我的项目中使用了它。然而,由于我使用的是64位电脑,而演示版本可能是针对x86的,所以我不得不逐个文件地复制代码。但在那之后,它运行得很好。 - samwa

3

我有同样的问题 - 我的应用程序中有多个昂贵的方法,需要对这些结果进行缓存。 以前我只是复制并粘贴了类似的代码,但后来我决定将此逻辑从我的领域中提取出来。

    static List<News> _topNews = null;
    static DateTime _topNewsLastUpdateTime = DateTime.MinValue;
    const int CacheTime = 5;  // In minutes

    public IList<News> GetTopNews()
    {
        if (_topNewsLastUpdateTime.AddMinutes(CacheTime) < DateTime.Now)
        {
            _topNews = GetList(TopNewsCount);
        }

        return _topNews;
    }

这就是现在我能够编写它的方式:

    public IList<News> GetTopNews()
    {
        return Cacher.GetFromCache(() => GetList(TopNewsCount));
    }

Cacher - 是一个简单的辅助类,这是它的代码:

public static class Cacher
{
    const int CacheTime = 5;  // In minutes

    static Dictionary<long, CacheItem> _cachedResults = new Dictionary<long, CacheItem>();

    public static T GetFromCache<T>(Func<T> action)
    {
        long code = action.GetHashCode();

        if (!_cachedResults.ContainsKey(code))
        {
            lock (_cachedResults)
            {
                if (!_cachedResults.ContainsKey(code))
                {
                    _cachedResults.Add(code, new CacheItem { LastUpdateTime = DateTime.MinValue });
                }
            }
        }

        CacheItem item = _cachedResults[code];
        if (item.LastUpdateTime.AddMinutes(CacheTime) >= DateTime.Now)
        {
            return (T)item.Result;
        }

        T result = action();

        _cachedResults[code] = new CacheItem
        {
            LastUpdateTime = DateTime.Now,
            Result = result
        };

        return result;
    }
}


class CacheItem
{
    public DateTime LastUpdateTime { get; set; }
    public object Result { get; set; }
}

关于Cacher的简介。你可能会注意到我在计算结果时没有使用Monitor.Enter() ( lock(...) )。这是因为复制CacheItem指针(return (T)_cachedResults[code].Result; 行)是线程安全的操作 - 它只需要一次操作即可完成。同时,如果多个线程同时更改此指针,也是可以的 - 它们都将是有效的。

但在另一个线程插入值时从字典(TryGetValue)读取是否安全?插入会改变字典的内部结构,在此期间从字典读取可能(我不确定)导致未定义的行为。 - Greg
Greg,我相信是这样的。TryGetValue 的内部实现只是在其数组中进行迭代,并搜索适合请求值的哈希键。如果数组发生更改,则不应发生任何事情。 - Vladimir
http://blogs.msdn.com/b/kimhamil/archive/2008/03/08/hashtable-and-dictionary-thread-safety-considerations.aspx - Greg
GetList(TopNewCount),GetList(5)和GetList(8)将产生相同的哈希码,导致返回无效结果,因为在存储结果时没有考虑传递的参数。 - Ash M

1
你可以在类中添加一个字典,使用逗号分隔的字符串作为键,函数名作为键名,结果作为值。然后当你的函数需要检查该值是否存在时,可以在字典中进行查询。将字典保存在缓存中,以便所有用户都可以访问。

函数名显然不足以作为关键字,它必须是参数相关的。但我相信,在方法开头添加简单的缓存/检索代码并不是OP所寻找的。 - Dyppl

1

据我所知,坦率地说,不行。

但是,在框架内实现这个功能对于所有人在所有情况下都能够通用而言,这将是一个相当大的工程 - 然而,您可以通过简单地(显然,简单性是相对于需求而言的)使用抽象、继承和现有的ASP.NET Cache来定制适合自己需求的东西。


1

如果您想创建一个可以贴在任何方法上的[Cache]属性(或类似属性),那么PostSharp是您的一站式解决方案。以前我使用PostSharp时,我无法忍受它会使我的构建变得非常缓慢(这是在2007年左右,所以现在可能已经不相关了)。

另一个解决方案是结合ASP.NET MVC中的Render.PartialOutputCaching来使用。这是为小部件/页面区域提供HTML的绝佳解决方案。

另一个解决方案是在MVC中将您的[Cache]属性实现为ActionFilterAttribute。这将允许您标记控制器方法进行缓存。它只适用于控制器方法,因为AOP魔术只能在MVC管道期间与ActionFilterAttributes一起发生。

通过ActionFilterAttribute实现AOP已经成为我们公司的首选解决方案。


0

如果您不需要属性配置而接受代码配置,也许 MbCache 就是您正在寻找的东西?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接