方法的延迟缓存(如 DB getter)

Lazy caching of a method (like a DB getter)

只是为了避免重新发明轮子 我想知道是否已经存在一个标准的 C# 实现来缓存来自 long-运行、资源密集型方法的结果。 在我看来, Lazy<T> 是合适的,但不幸的是它似乎缺少输入参数来索引结果。 我希望以下内容有助于澄清:这是我的自定义解决方案。

public class Cached<FromT,ToT>
{
    private Func<FromT,ToT> _my_func;
    private Dictionary<FromT,ToT> funcDict;
    public Cached(Func<FromT,ToT> coreFunc, IEqualityComparer<FromT> comparer = null)
    {
        _my_func = coreFunc;
        if (comparer != null) {
            funcDict = new Dictionary<FromT,ToT>(comparer);
        } else {
            funcDict = new Dictionary<FromT,ToT>();
        }
    }
    public ToT Get(FromT fromKey) {
        if (!funcDict.ContainsKey(fromKey)) {
            funcDict.Add(fromKey, _my_func(fromKey) );
        }
        return funcDict[fromKey];
    }
}

在下面找到我的单元测试代码。

string DBSimulation(int example, bool quick = false) {
    if (!quick) Thread.Sleep(15000);
    return example.ToString();
}

[Test]
public void Test03Cached() {
    var testCache = new Functional.Cached<int,string>(x => DBSimulation(x));
    DateTime checkNow = DateTime.Now;
    string logResult = "";
    for (int i = 0; i < 24; i++) {
        Assert.AreEqual(DBSimulation( i % 3, true), testCache.Get( i % 3));
        logResult += String.Format("After {0} seconds => {1} returned from {2} \n",
                                   ((TimeSpan)(DateTime.Now - checkNow)).TotalSeconds,
                                   testCache.Get( i % 3), i);
    }
    Console.WriteLine(logResult);
    double elapsed = ((TimeSpan)(DateTime.Now - checkNow)).TotalSeconds;
    Assert.LessOrEqual(elapsed, 15*3+1,"cache not working, {0} seconds elapsed", elapsed);
}

及其输出

After 15,0035002 seconds => 1 returned from 1 
After 30,0050002 seconds => 2 returned from 2 
After 45,0065002 seconds => 0 returned from 3 
After 45,0065002 seconds => 1 returned from 4 
After 45,0065002 seconds => 2 returned from 5 
... 
After 45,0065002 seconds => 0 returned from 21 
After 45,0065002 seconds => 1 returned from 22 
After 45,0065002 seconds => 2 returned from 23 

编辑

对于通用的 FromT,字典需要一个 IEqualityComparer

是的,不错的解决方案,但我为您提供了一些关于并发性的改进(ConcurrentDictionary)。例如,您可以在 asp.net 应用程序中使用此代码,其中许多线程可以同时使用此 class。此外,由于此 class 将用于长时间运行的函数,因此不等待结果而是稍后处理结果(Task.ContinueWith)将是一个很好的功能,当函数将完成时:

public class Cached<FromT, ToT>
{
    private Func<FromT, ToT> _my_func;
    private ConcurrentDictionary<FromT, ToT> funcDict = new ConcurrentDictionary<FromT, ToT>();
    private Random rand = new Random();

    public Cached(Func<FromT, ToT> coreFunc)
    {
        _my_func = coreFunc;
    }

    public Task<ToT> Get(FromT fromKey)
    {
        if (!funcDict.ContainsKey(fromKey))
            return Task.Factory.StartNew(() => {
                var result = _my_func(fromKey);
                do
                {
                    if (!funcDict.ContainsKey(fromKey) && !funcDict.TryAdd(fromKey, result))                        
                        Thread.Sleep(rand.Next(50, 100));                                                    
                    else
                        break;
                } while (true);                    
                return result;
            });
        ToT answer;
        funcDict.TryGetValue(fromKey, out answer);
        return Task.FromResult(answer);
    }
}

public static string DBSimulation(int example)
{
    Thread.Sleep(15000);
    return example.ToString();
}

public static void Main()
{
    var testCache = new Cached<int, string>(x => DBSimulation(x));
    DateTime checkNow = DateTime.Now;
    for (int i = 0; i < 24; i++)
    {
        var j = i;
        testCache.Get(i % 3).ContinueWith(x => 
            Console.WriteLine(String.Format("After {0} seconds => {1} returned from {2}",
                                           ((TimeSpan)(DateTime.Now - checkNow)).TotalSeconds,
                                           x.Result, j)));
    }            
    Console.ReadKey();        
}

结果:

After 15.0164309 seconds => 0 returned from 6
After 15.0164309 seconds => 2 returned from 5
After 15.0164309 seconds => 1 returned from 4
After 15.0164309 seconds => 0 returned from 3
After 15.0164309 seconds => 0 returned from 0
......
After 26.5133477 seconds => 1 returned from 19
After 27.5112726 seconds => 2 returned from 20
After 28.5127277 seconds => 0 returned from 21
After 29.5126096 seconds => 1 returned from 22
After 30.0204739 seconds => 2 returned from 23

P.S. as you can see time elapsed to perform all operations reduced from 45 to 30 seconds, not to 15, because it depends on number of cores, number of started threads and other stuff, also some of time is expended to dictionary's synchronization.