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

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

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

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

Func 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 Memoize(this Func f)
{
  var map = new Dictionary();
  return a =>
    {
      R value;
      lock(map) 
      {
        if (map.TryGetValue(a, out value))
          return value;
        value = f(a);
        map.Add(a, value);
      }
        return value;
    };
}

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

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

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

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

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

public static Func Memoize(this Func f)
{
  var map = new Dictionary();
  var mapSync = new Dictionary();
  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)

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