懒 毫无例外地缓存

问题描述 投票:8回答:4

是否有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”消息。我们应该看到6次消息“LightsaberProvider.GetFor jedi:x”,因为每个键一次+一次键“2”失败。
c# .net multithreading caching lazy-evaluation
4个回答
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关闭时,也许我们会内置这个功能。


5
投票

不幸的是,这是错误的解决请忽略它并使用tsul答案。只有当你想调试它并发现bug时才离开它。

这是使用tsul SimpleLazy:https://dotnetfiddle.net/Y2GP2z的工作解决方案(与工厂并发缓存)


我最终得到了以下解决方案:包装Lazy以模仿与Lazy相同的功能但没有异常缓存。

这是LazyWithoutExceptionsCaching类:

public class LazyWithoutExceptionCaching<T>
{
    private readonly Func<T> _valueFactory;
    private Lazy<T> _lazy;

    public LazyWithoutExceptionCaching(Func<T> valueFactory)
    {
        _valueFactory = valueFactory;
        _lazy = new Lazy<T>(valueFactory);
    }

    public T Value
    {
        get
        {
            try
            {
                return _lazy.Value;
            }
            catch (Exception)
            {
                _lazy = new Lazy<T>(_valueFactory);
                throw;
            }
        }
    }
}

完整的工作示例(FIDDLE it here):

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

namespace Rextester
{
    public class Program
    {
        public class LazyWithoutExceptionCaching<T>
        {
            private readonly Func<T> _valueFactory;
            private Lazy<T> _lazy;

            public LazyWithoutExceptionCaching(Func<T> valueFactory)
            {
                _valueFactory = valueFactory;
                _lazy = new Lazy<T>(valueFactory);
            }

            public T Value
            {
                get
                {
                    try
                    {
                        return _lazy.Value;
                    }
                    catch (Exception)
                    {
                        _lazy = new Lazy<T>(_valueFactory);
                        throw;
                    }
                }
            }
        }

        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, LazyWithoutExceptionCaching<string>> _producedLightsabers;

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

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

        public static void Main(string[] args)
        {
            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);
                }
            });
        }
    }
}

2
投票

正如我在评论中提到的,您可以使用TPL libraryTask对象来简化代码:

var resultTask = Task.Factory.StartNew(new Action<object>(
  (x) => GetFor(x)), rawData);
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);
}

在那之后,你可以像这样wait for the result这个任务:

resultTask.Wait();

这将缓存混凝土x的操作结果。如果任务正确运行,您可以检查Result属性。如果任务失败,Exception属性将存储具有内部实际异常的AggregateExceptionResult被缓存,不会重新计算。如果任务失败,它将调用throw属性或其他一些阻塞方法来Result异常。如果需要不同参数的结果,则应创建新任务。

我鼓励你检查一下这个库,因为你将节省你重新发明轮子的时间:)另外你还有一些开箱即用的功能,如多线程,异常处理,任务取消等等。祝你的项目顺利:)


1
投票

实际上,这个功能有争议:qazxsw poi

等待,我使用Marius Gundersen的优雅实现:https://github.com/dotnet/corefx/issues/32337

https://github.com/alastairtree/LazyCache/issues/73

-1
投票

更好的方法:

public class AtomicLazy<T>
{
    private readonly Func<T> _factory;
    private T _value;
    private bool _initialized;
    private object _lock;

    public AtomicLazy(Func<T> factory)
    {
        _factory = factory;
    }

    public T Value => LazyInitializer.EnsureInitialized(ref _value, ref _initialized, ref _lock, _factory);
}
© www.soinside.com 2019 - 2024. All rights reserved.