这里堆栈溢出我found记住单参数函数的代码:
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;
};
}
虽然这段代码对我来说很有用,但是当同时从多个线程调用memoized函数时,它会失败:Add
方法被相同的参数调用两次并抛出异常。
如何使memoization线程安全?
答案 0 :(得分:22)
您可以使用ConcurrentDictionary.GetOrAdd
来完成您需要的一切:
static Func<A, R> ThreadsafeMemoize<A, R>(this Func<A, R> f)
{
var cache = new ConcurrentDictionary<A, R>();
return argument => cache.GetOrAdd(argument, f);
}
函数f
本身应该是线程安全的,因为它可以同时从多个线程调用。
此代码也不保证每个唯一参数值只调用一次函数f
。事实上,在繁忙的环境中可以多次调用它。如果你需要这种合同,你应该看看这个related question中的答案,但要注意它们不是那么紧凑并且需要使用锁。
答案 1 :(得分:2)
就像Gman提到的那样ConcurrentDictionary
是首选的方法,但是如果简单的lock
语句不可用,那就足够了。
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;
};
}
使用锁而不是ConcurrentDictionary
的一个潜在问题是此方法可能会在程序中引入死锁。
_memo1 = Func1.Memoize()
和_memo2 = Func2.Memoize()
,其中_memo1
和_memo2
是实例变量。_memo1
,Func1
开始处理。_memo2
,在Func2
内调用_memo1
和Thread2阻止。Func1
的处理在函数后期调用_memo2
,Thread1阻塞。因此,如果可能的话,使用ConcurrentDictionary
,但是如果你不能并且你使用锁而不是在内置Memoized函数时调用其他在你运行的函数之外的其他Memoized函数或者你让自己面临死锁的风险(如果_memo1
和_memo2
是局部变量而不是实例变量,则不会发生死锁。)
(注意,使用ReaderWriterLock
可能会略微改善性能,但您仍会遇到相同的死锁问题。)
答案 2 :(得分:2)
在GMan's answer上扩展,我想记住一个带有多个参数的函数。这是我的操作方式,使用C#Tuple
(需要C#7)作为ConcurrentDictionary
的键。
可以轻松扩展此技术以允许更多参数:
public static class FunctionExtensions
{
// Function with 1 argument
public static Func<TArgument, TResult> Memoize<TArgument, TResult>
(
this Func<TArgument, TResult> func
)
{
var cache = new ConcurrentDictionary<TArgument, TResult>();
return argument => cache.GetOrAdd(argument, func);
}
// Function with 2 arguments
public static Func<TArgument1, TArgument2, TResult> Memoize<TArgument1, TArgument2, TResult>
(
this Func<TArgument1, TArgument2, TResult> func
)
{
var cache = new ConcurrentDictionary<(TArgument1, TArgument2), TResult>();
return (argument1, argument2) =>
cache.GetOrAdd((argument1, argument2), tuple => func(tuple.Item1, tuple.Item2));
}
}
例如:
Func<int, string> example1Func = i => i.ToString();
var example1Memoized = example1Func.Memoize();
var example1Result = example1Memoized(66);
Func<int, int, int> example2Func = (a, b) => a + b;
var example2Memoized = example2Func.Memoize();
var example2Result = example2Memoized(3, 4);
(当然,为了获得记忆的好处,您通常希望将example1Memoized
/ example2Memoized
保留在类变量中或它们并非短暂的地方)。
答案 3 :(得分:0)
使用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;
}