System.Lazy<T>
?或者其他用于懒惰多线程初始化和缓存的好方法?我有以下程序(在这里测试):
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 ,具有LazyThreadSafetyMode.ExecutionAndPublication选项,但没有异常缓存。
总之,必须满足以下技术要求:
- 我们希望拥有线程安全的缓存。 - 缓存是键值缓存。让我们简化它,键的类型为字符串类型,值也是字符串类型。 - 生产一个项目非常昂贵 - 因此对于给定的键,生产必须由一个且仅有一个线程开始。键“a”的生产不会阻止键“b”的生产。 - 如果生产成功结束 - 我们希望缓存生成的项目。 - 如果在生产过程中抛出异常 - 我们希望将异常传递给调用者。调用者的责任是决定重试/放弃/记录。异常不会被缓存 - 对于此项的下一次缓存调用将启动该项的生产。
在我的例子中:
- 我们有LightsabersCache,LightsabersCache.GetLightsaber方法获取给定键的值。 - LightsaberProvider只是一个虚拟提供者。它模仿生产性质:生产是昂贵的(2秒),有时(在此情况下仅第一次,对于key =“2”)会抛出异常。 - 该程序启动15个线程,每个线程尝试10次从范围<0;4>获取值。仅有一次异常被抛出,因此我们应该只看到一次“Dark side happened...”。在<0;4>范围内有5个键,因此控制台上应只有5个“Lightsaber produced”的消息。我们应该看到6次消息“LightsaberProvider.GetFor jedi:x”,因为每个键一次+键“2”失败一次。
Task
。 - VMAtm