加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

c# – 懒惰,无异常缓存

发布时间:2020-12-15 08:16:58 所属栏目:百科 来源:网络整理
导读:是否有System.Lazy T无一例外地缓存?或者懒惰多线程初始化的另一个很好的解决方案缓存? 我有以下程序(fiddle it here): using System;using System.Collections.Concurrent;using System.Threading;using System.Threading.Tasks;using System.Net;namesp
是否有System.Lazy< T>无一例外地缓存?或者懒惰多线程初始化的另一个很好的解决方案&缓存?

我有以下程序(fiddle it here):

using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using System.Net;

namespace ConsoleApplication3
{
    public class Program
    {
        public class LightsaberProvider
        {
            private static int _firstTime = 1;

            public LightsaberProvider()
            {
                Console.WriteLine("LightsaberProvider ctor");
            }

            public string GetFor(string jedi)
            {
                Console.WriteLine("LightsaberProvider.GetFor jedi: {0}",jedi);

                Thread.Sleep(TimeSpan.FromSeconds(1));
                if (jedi == "2" && 1 == Interlocked.Exchange(ref _firstTime,0))
                {
                    throw new Exception("Dark side happened...");
                }

                Thread.Sleep(TimeSpan.FromSeconds(1));
                return string.Format("Lightsaver for: {0}",jedi);
            }
        }

        public class LightsabersCache
        {
            private readonly LightsaberProvider _lightsaberProvider;
            private readonly ConcurrentDictionary<string,Lazy<string>> _producedLightsabers;

            public LightsabersCache(LightsaberProvider lightsaberProvider)
            {
                _lightsaberProvider = lightsaberProvider;
                _producedLightsabers = new ConcurrentDictionary<string,Lazy<string>>();
            }

            public string GetLightsaber(string jedi)
            {
                Lazy<string> result;
                if (!_producedLightsabers.TryGetValue(jedi,out result))
                {
                    result = _producedLightsabers.GetOrAdd(jedi,key => new Lazy<string>(() =>
                    {
                        Console.WriteLine("Lazy Enter");
                        var light = _lightsaberProvider.GetFor(jedi);
                        Console.WriteLine("Lightsaber produced");
                        return light;
                    },LazyThreadSafetyMode.ExecutionAndPublication));
                }
                return result.Value;
            }
        }

        public void Main()
        {
            Test();
            Console.WriteLine("Maximum 1 'Dark side happened...' strings on the console there should be. No more,no less.");
            Console.WriteLine("Maximum 5 lightsabers produced should be. No more,no less.");
        }

        private static void Test()
        {
            var cache = new LightsabersCache(new LightsaberProvider());

            Parallel.For(0,15,t =>
            {
                for (int i = 0; i < 10; i++)
                {
                    try
                    {
                        var result = cache.GetLightsaber((t % 5).ToString());
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(e.Message);
                    }
                    Thread.Sleep(25);
                }
            });
        }
    }
}

基本上我想缓存生产的光剑,但生产它们既昂贵又棘手 – 有时会出现例外情况.我想在给定的jedi时只允许一个生产者,但是当抛出异常时 – 我希望另一个生产者再试一次.因此,期望的行为类似于System.Lazy< T>.使用LazyThreadSafetyMode.ExecutionAndPublication选项,但没有例外缓存.

总而言之,必须满足以下技术要求:

>我们想要一个线程安全的缓存
>缓存是键值缓存.让我们简化它,键是字符串的类型,值也是字符串的类型
>生产一个项目是昂贵的 – 因此生产必须由一个且只有一个线程开始给定密钥.密钥“a”的生产不会阻止密钥“b”的生产
>如果生产成功结束 – 我们想要缓存生产的项目
>如果在抛出生产异常期间 – 我们希望将异常传递给调用者.呼叫者的责任是决定重试/放弃/记录.异常未缓存 – 下次调用此项目的缓存将启动项目生成.

在我的例子中:

>我们有LightsabersCache,LightsabersCache.GetLightsaber方法获取给定键的值
> LightsaberProvider只是虚拟提供者.它模仿生产性质:生产很昂贵(2秒),有时(在这种情况下只是第一次,对于key =“2”)异常被抛出
>程序启动15个线程,每个线程尝试10次以从范围< 0; 4>获取值.只抛出一次异常,所以只有一次我们应该看到“黑暗面发生了……”.在< 0; 4>范围内有5个键.所以控制台上只能有5个“Lightsaber产生的”消息.我们应该看到消息“LightsaberProvider.GetFor jedi:x”的6倍,因为每个密钥的一次因密钥“2”而失败.

解决方法

这很难用 built-in Lazy:你应该将你的 LazyWithoutExceptionCaching.Value吸气剂包裹起来.但这使得内置Lazy的使用变得多余:你将在Lazy.Value getter中拥有不必要的锁.

最好自己编写自己的Lazy实现
如果你打算只实例化引用类型,它会变得相当简单:

public class SimpleLazy<T> where T : class
{
    private readonly Func<T> valueFactory;
    private T instance;
    private readonly object locker = new object();

    public SimpleLazy(Func<T> valueFactory)
    {
        this.valueFactory = valueFactory;
        this.instance = null;
    }

    public T Value
    {
        get
        {
            lock (locker)
                return instance ?? (instance = valueFactory());
        }
    }
}

附:也许我们将在this issue关闭时内置此功能.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读