方法的延迟缓存(例如DB getter) [英] Lazy caching of a method (like a DB getter)

查看:97
本文介绍了方法的延迟缓存(例如DB getter)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Just to avoid reinventing the wheel I'm wondering whether a standard C# implementation already exists to cache the results from a long-running, resource-intensive method. To my mind, the Lazy<T> would be appropriate, but unfortunately it seems to lack the input parameter to index the result. I hope the following helps to clarify: this is my custom solution.

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

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

Find below my unit-testing code.

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

For a generic FromT an IEqualityComparer is needed for the Dictionary

推荐答案

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

Yes, not bad solution, but I offer you some improvements on concurrency(ConcurrentDictionary). You can use this code, for example, at your asp.net application where many threads simultaneously can use this class. Also as this class will be used for longrunning functions it will be good feature to not wait result but instead process result later(Task.ContinueWith) when function will be completed:

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.如您所见,执行所有操作所花费的时间减少了 从45到30秒,而不是15秒,因为这取决于 核心,启动线程的数量和其他内容,还有一些时间是 扩展到字典的同步.

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.

这篇关于方法的延迟缓存(例如DB getter)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆