Here on stack overflow I've found the code that memoizes single-argument functions:
static Func<A, R> Memoize<A, R>(this Func<A, R> f)
{
var d = new Dictionary<A, R>();
return a=>
{
R r;
if (!d.TryGetValue(a, out r))
{
r = f(a);
d.Add(a, r);
}
return r;
};
}
While this code does its job for me, it fails sometimes when the memoized function is called from the multiple threads simultaneously: the Add
method gets called twice with the same argument and throws an exception.
How can I make the memoization thread-safe?
You can use ConcurrentDictionary.GetOrAdd
which does everything you need:
static Func<A, R> ThreadsafeMemoize<A, R>(this Func<A, R> f)
{
var cache = new ConcurrentDictionary<A, R>();
return argument => cache.GetOrAdd(argument, f);
}
The function f
should be thread-safe itself, because it can be called from multiple threads simultaneously.
This code also doesn't guarantee that function f
is called only once per unique argument value. It can be called many times, in fact, in the busy environment. If you need this kind of contract, you should take a look at the answers in this related question, but be warned that they're not as compact and require using locks.
Like Gman mentioned ConcurrentDictionary
is the preferred way to do this, however if that is not available to a simple lock
statement would suffice.
static Func<A, R> Memoize<A, R>(this Func<A, R> f)
{
var d = new Dictionary<A, R>();
return a=>
{
R r;
lock(d)
{
if (!d.TryGetValue(a, out r))
{
r = f(a);
d.Add(a, r);
}
}
return r;
};
}
One potential issue using locks instead of ConcurrentDictionary
is this method could introduce deadlocks in to your program.
- You have two memoized functions
_memo1 = Func1.Memoize()
and _memo2 = Func2.Memoize()
, where _memo1
and _memo2
are instance variables.
- Thread1 calls
_memo1
, Func1
starts processing.
- Thread2 calls
_memo2
, inside Func2
there is a call to _memo1
and Thread2 blocks.
- Thread1's processing of
Func1
gets to a call of _memo2
late in the function, Thread1 blocks.
- DEADLOCK!
So if at all possible, use ConcurrentDictionary
, but if you can't and you use locks instead do not call other Memoized functions that are scoped outside of the function you are running in when inside Memoized functions or you open yourself up to the risk of deadlocks (if _memo1
and _memo2
been local variables instead of instance variables the deadlock would not have happened).
(Note, performance may be slightly improved by using ReaderWriterLock
but you still will have the same deadlock issue.)
using System.Collections.Generic;
Dictionary<string, string> _description = new Dictionary<string, string>();
public float getDescription(string value)
{
string lookup;
if (_description.TryGetValue (id, out lookup)) {
return lookup;
}
_description[id] = value;
return lookup;
}