Потокобезопасное напоминание

Давайте возьмемУэс Дайер Подход к функции запоминания в качестве отправной точки:

public static Func<A, R> Memoize<A, R>(this Func<A, R> f)
{
  var map = new Dictionary<A, R>();
  return a =>
    {
      R value;
      if (map.TryGetValue(a, out value))
        return value;
      value = f(a);
      map.Add(a, value);
      return value;
    };
}

Проблема в том, что при использовании его из нескольких потоков мы можем столкнуться с проблемами:

Func<int, int> f = ...
var f1 = f.Memoize();
...
in thread 1:
var y1 = f1(1);
in thread 2:
var y2 = f1(1);
// We may be recalculating f(1) here!

Давайте попробуем избежать этого. Блокировка наmap:

public static Func<A, R> Memoize<A, R>(this Func<A, R> f)
{
  var map = new Dictionary<A, R>();
  return a =>
    {
      R value;
      lock(map) 
      {
        if (map.TryGetValue(a, out value))
          return value;
        value = f(a);
        map.Add(a, value);
      }
        return value;
    };
}

это явно ужасная идея, потому что она мешает нам вычислитьf1 на многихdifferent аргументы сразу. Блокировка наa не будет работать, еслиa имеет тип значения (и, во всяком случае, это плохая идея, так как мы не контролируемa и внешний код может заблокировать его тоже).

Вот два варианта, которые я могу придумать:

ПредполагаяLazy<T> класс для ленивых оценки (см.Вот):

public static Func<A, R> Memoize<A, R>(this Func<A, R> f)
{
  var map = new Dictionary<A, Lazy<R>>();
  return a =>
    {
      Lazy<R> result;
      lock(map) 
      {
        if (!map.TryGetValue(a, out result))
        {  
          result = () => f(a);
          map.Add(a, result);
        }
      }
      return result.Value;
    };
}

Или ведение дополнительного словаря объектов для синхронизации:

public static Func<A, R> Memoize<A, R>(this Func<A, R> f)
{
  var map = new Dictionary<A, R>();
  var mapSync = new Dictionary<A, object>();
  return a =>
    {
      R value;
      object sync;
      lock(mapSync)
      { 
        if (!mapSync.TryGetValue(a, out sync))
        { 
          sync = new object();
          mapSync[a] = sync;
        }
      }
      lock(map)
      {
        if (map.TryGetValue(a, out value))
          return value;
      }
      lock(sync)
      {
        value = f(a);
        lock(map)
        {
          map[a] = value;
        }
        return value;
      }
    };
}

Есть лучшие варианты?

Ответы на вопрос(7)

Ваш ответ на вопрос