Come Gman menzionato ConcurrentDictionary
è il metodo preferito per eseguire questa operazione, tuttavia se ciò non è disponibile per una semplice istruzione lock
è sufficiente.
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;
};
}
Un potenziale problema con serrature invece di ConcurrentDictionary
è questo metodo potrebbe introdurre deadlock al tuo programma.
- Si hanno due funzioni memoized
_memo1 = Func1.Memoize()
e _memo2 = Func2.Memoize()
, dove _memo1
e _memo2
sono variabili di istanza.
- Thread1 chiama
_memo1
, Func1
inizia l'elaborazione.
- Thread2 chiama
_memo2
, all'interno di Func2
c'è una chiamata a _memo1
e blocchi di Discussione2.
- L'elaborazione di Thread1 di
Func1
arriva a una chiamata di _memo2
in ritardo nella funzione, blocchi Thread1.
- DEADLOCK!
Quindi, se possibile, utilizzare ConcurrentDictionary
, ma se non si può e si utilizza serrature invece non chiamare altre funzioni Memoized che hanno un ambito al di fuori della funzione che si esegue quando all'interno Memoized funzioni o si apre fino al rischio di deadlock (se _memo1
e _memo2
sono state variabili locali anziché variabili di istanza, il deadlock non si sarebbe verificato).
(Nota, la prestazione può essere leggermente migliorata usando ReaderWriterLock
ma è ancora avrà lo stesso problema di stallo.)
fonte
2013-12-12 15:58:01
Nota che 'GetOrAdd' non impedirà completamente che f venga chiamato più di una volta per un determinato argomento; garantisce solo che il risultato di solo * uno * delle invocazioni venga aggiunto al dizionario. È possibile ottenere più di una chiamata nel caso in cui i thread controllino la cache contemporaneamente prima che il valore memorizzato nella cache sia stato aggiunto. Spesso non vale la pena preoccuparsi di questo, ma lo menziono nel caso in cui l'invocazione abbia effetti collaterali indesiderati. –
@JamesWorld Sì, è vero. Risposta modificata per riflettere questo, grazie! – Gman
Sono un po 'confuso - non è 'cache' qui una variabile locale? Ogni volta che viene chiamato 'ThreadsafeMemoize()', non creerà un nuovo dizionario? – dashnick