是否有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
选项,但没有例外缓存。
总而言之,必须满足以下技术要求:
在我的例子中:
使用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关闭时,也许我们会内置这个功能。
不幸的是,这是错误的解决请忽略它并使用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);
}
});
}
}
}
正如我在评论中提到的,您可以使用TPL library的Task
对象来简化代码:
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
属性将存储具有内部实际异常的AggregateException
。 Result
被缓存,不会重新计算。如果任务失败,它将调用throw
属性或其他一些阻塞方法来Result
异常。如果需要不同参数的结果,则应创建新任务。
我鼓励你检查一下这个库,因为你将节省你重新发明轮子的时间:)另外你还有一些开箱即用的功能,如多线程,异常处理,任务取消等等。祝你的项目顺利:)
实际上,这个功能有争议:qazxsw poi
等待,我使用Marius Gundersen的优雅实现:https://github.com/dotnet/corefx/issues/32337
https://github.com/alastairtree/LazyCache/issues/73
更好的方法:
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);
}