是否存在无异常缓存的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>.只有一次异常被抛出,所以只有一次我们应该看到“Dark side happened..."。在<0的范围内有5个键; 4>所以只有5个“Lightsaber produced”消息应该在控制台上。我们应该看到6次消息“LightsaberProvider.GetFor jedi:因为每个键一次+键“2”一次失败。
7条答案
按热度按时间lmyy7pcs1#
实际上,这个功能是有争议的:https://github.com/dotnet/corefx/issues/32337
为了等待,我使用Marius Gundersen的这个优雅的实现:https://github.com/alastairtree/LazyCache/issues/73
cnwbcb6i2#
很难使用built-in Lazy来实现:你应该把你的LazyWithoutExceptionCaching.Value getter包在一个锁里。但是这使得使用内置的
Lazy
变得多余:你会在Lazy.Value
getter里面有不必要的锁。最好编写自己的Lazy实现,特别是如果你只打算示例化引用类型,它变得相当简单:
P.S.当this issue关闭时,我们可能会内置此功能。
1zmg4dgp3#
不幸的是,这是错误的解决方案!请忽略它并使用tul回答。只有当你想调试它并发现bug时才留下它。
下面是使用tsulSimpleLazy的工作解决方案(使用工厂的并发缓存):https://dotnetfiddle.net/Y2GP2z
我最终得到了以下解决方案: Package Lazy以模仿Lazy的相同功能,但没有例外缓存。
下面是LazyWithoutExceptionsCaching类:
完整工作示例(FIDDLE it here):
v09wglhw4#
vernou和tsul(分别为
AtomicLazy<T>
和SimpleLazy<T>
)的两个现有答案充分解决了这个问题,但它们都表现出不完全符合我的喜好的行为。如果valueFactory
失败,当前处于休眠模式等待Value
的所有线程将逐个重试valueFactory
。这意味着,例如,如果100个线程同时请求Value
,并且valueFactory
在失败之前需要1秒,则valueFactory
将被调用100次,并且列表中的最后一个线程将等待100秒才获得异常。在我看来,更好的行为是将
valueFactory
的错误传播到当前正在等待的所有线程。这样,没有线程等待响应的时间会超过单个valueFactory
调用的持续时间。下面是一个具有此行为的LazyWithRetry<T>
类的实现:LazyWithRetry<T>
类的演示可以在here中找到。下面是此演示的示例输出:下面是使用
AtomicLazy<T>
或SimpleLazy<T>
类时相同演示的示例输出:iyr7buue5#
正如我在注解中提到的,您可以通过使用TPL library的
Task
对象来简化代码:然后,你可以像这样wait for the result这个任务:
执行此操作将缓存具体
x
的操作结果。如果任务运行正常,则可以检查Result
属性。如果任务失败,Exception
属性将存储带有内部实际异常的AggregateException
。Result
已缓存,不会重新计算。如果任务失败,它将在调用Result
属性或它的其他一些阻塞方法时throw
它的异常。如果你需要不同参数的结果,你应该创建一个新任务。我鼓励你检查这个库,因为你会保存你的时间来重新发明轮子:)你也会得到一些开箱即用的功能,如多线程,异常处理,任务取消等等。祝你的项目好运:)
fdx2calv6#
更好的方法:
0yg35tkg7#
基于@piotrwest创建了这个类作为改进!
使用LazyThreadSafetyMode.PublicationOnly配置Lazy示例可以重试,直到获得所需的值,但它也允许同时调用多个Create函数。为了应对这种机制,我添加了一个引用计数器,以允许同时只调用一个valueFactory。只有在可以从Value属性管理故障的情况下才应考虑使用此选项。