У меня есть следующая программа (поиграйте с ней здесь):
Код: Выделить всё
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 _producedLightsabers;
public LightsabersCache(LightsaberProvider lightsaberProvider)
{
_lightsaberProvider = lightsaberProvider;
_producedLightsabers = new ConcurrentDictionary();
}
public string GetLightsaber(string jedi)
{
Lazy result;
if (!_producedLightsabers.TryGetValue(jedi, out result))
{
result = _producedLightsabers.GetOrAdd(jedi, key => new Lazy(() =>
{
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);
}
});
}
}
}
В целом, должны быть выполнены следующие технические требования:
- нам нужен поточно-ориентированный кеш
- кэш кеш-ключ-значение. Давайте упростим это, и ключ - это тип строки, а значение также является типом строки.
- производство элемента является дорогостоящим - поэтому производство должно быть запущено одним и только одним потоком для данного ключа. Производство для ключа "a" не блокирует производство для ключа "b"
- если производство завершилось успешно - мы хотим кэшировать произведенный элемент
- если во время производства выдается исключение - мы хотим передать исключение вызывающей стороне. Ответственность вызывающего абонента состоит в том, чтобы принять решение о повторной попытке/отказе/регистрации. Исключение не кэшируется — следующий вызов кэша для этого элемента запустит создание элемента.
- у нас есть метод LightsabersCache, LightsabersCache.GetLightsaber получает значение для заданного ключа
- LightsaberProvider — это всего лишь фиктивный поставщик . Это имитирует производственный характер: производство является дорогостоящим (2 секунды), и иногда (в данном случае только в первый раз, для key="2") выдается исключение
- программа запускает 15 потоков и каждый поток пытается 10 раз получить значение из диапазона . Выдается только одно исключение, поэтому только один раз мы должны увидеть «Произошла темная сторона…». В диапазоне имеется 5 ключей, поэтому на консоли должно быть только 5 сообщений «Создан световой меч». Мы должны увидеть сообщение «LightsaberProvider.GetFor jedi: x» 6 раз, потому что один раз для каждого ключа + один раз для ключа «2».
Подробнее здесь: https://stackoverflow.com/questions/343 ... on-caching