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);
}
});
}
}
}
По сути, я хочу кэшировать изготовленные световые мечи, но их производство дорого и сложно — иногда могут быть исключения. Я хочу разрешить одновременно только одному производителю для данного джедая, но когда выдается исключение, я хочу, чтобы другой продюсер попробовал еще раз. Таким образом, желаемое поведение аналогично System.Lazy с параметром LazyThreadSafetyMode.ExecutionAndPublication, но без кэширования исключений.
В целом, должны быть выполнены следующие технические требования:
нам нужен поточно-ориентированный кеш
кэш кеш-ключ-значение. Давайте упростим это, и ключ - это тип строки, а значение также является типом строки.
производство элемента является дорогостоящим - поэтому производство должно быть запущено одним и только одним потоком для данного ключа. Производство для ключа "a" не блокирует производство для ключа "b"
если производство завершилось успешно - мы хотим кэшировать произведенный элемент
если во время производства выдается исключение - мы хотим передать исключение вызывающей стороне. Ответственность вызывающего абонента состоит в том, чтобы принять решение о повторной попытке/отказе/регистрации. Исключение не кэшируется — следующий вызов кэша для этого элемента запустит создание элемента.
В моем примере:
у нас есть метод LightsabersCache, LightsabersCache.GetLightsaber получает значение для заданного ключа
LightsaberProvider — это всего лишь фиктивный поставщик . Это имитирует производственный характер: производство является дорогостоящим (2 секунды), и иногда (в данном случае только в первый раз, для key="2") выдается исключение
программа запускает 15 потоков и каждый поток пытается 10 раз получить значение из диапазона . Выдается только одно исключение, поэтому только один раз мы должны увидеть «Произошла темная сторона…». В диапазоне имеется 5 ключей, поэтому на консоли должно быть только 5 сообщений «Создан световой меч». Мы должны увидеть сообщение «LightsaberProvider.GetFor jedi: x» 6 раз, потому что один раз для каждого ключа + один раз для ключа «2».
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); } }); } } } [/code]
По сути, я хочу кэшировать изготовленные световые мечи, но их производство дорого и сложно — иногда могут быть исключения. Я хочу разрешить одновременно только одному производителю для данного джедая, но когда выдается исключение, я хочу, чтобы другой продюсер попробовал еще раз. Таким образом, желаемое поведение аналогично System.Lazy с параметром LazyThreadSafetyMode.ExecutionAndPublication, но без кэширования исключений.
В целом, должны быть выполнены следующие технические требования:
[list] [*]нам нужен поточно-ориентированный кеш [*]кэш кеш-ключ-значение. Давайте упростим это, и ключ - это тип строки, а значение также является типом строки. [*]производство элемента является дорогостоящим - поэтому производство должно быть запущено одним и только одним потоком для данного ключа. Производство для ключа "a" не блокирует производство для ключа "b" [*]если производство завершилось успешно - мы хотим кэшировать произведенный элемент [*]если во время производства выдается исключение - мы хотим передать исключение вызывающей стороне. Ответственность вызывающего абонента состоит в том, чтобы принять решение о повторной попытке/отказе/регистрации. Исключение не кэшируется — следующий вызов кэша для этого элемента запустит создание элемента. [/list]
В моем примере:
[list] [*]у нас есть метод LightsabersCache, LightsabersCache.GetLightsaber получает значение для заданного ключа [*]LightsaberProvider — это всего лишь фиктивный поставщик . Это имитирует производственный характер: производство является дорогостоящим (2 секунды), и иногда (в данном случае только в первый раз, для key="2") выдается исключение [*]программа запускает 15 потоков и каждый поток пытается 10 раз получить значение из диапазона . Выдается только одно исключение, поэтому только один раз мы должны увидеть «Произошла темная сторона…». В диапазоне имеется 5 ключей, поэтому на консоли должно быть только 5 сообщений «Создан световой меч». Мы должны увидеть сообщение «LightsaberProvider.GetFor jedi: x» 6 раз, потому что один раз для каждого ключа + один раз для ключа «2». [/list]