Si può assolutamente essere thread-safe nell'uso del dizionario immutabile. La stessa struttura dati è perfettamente thread-safe, ma è necessario scrivere attentamente le modifiche ad esso in un ambiente multi-thread per evitare la perdita di dati nel proprio codice.
Ecco uno schema che uso frequentemente solo per uno scenario del genere. Non richiede serrature, poiché l'unica mutazione che facciamo è un singolo incarico di memoria. Se devi impostare più campi, devi usare un lucchetto.
using System.Threading;
public class Something {
private ImmutableDictionary<string, string> dict = ImmutableDictionary<string, string>.Empty;
public void Add(string key, string value) {
// It is important that the contents of this loop have no side-effects
// since they can be repeated when a race condition is detected.
do {
var original = _dict;
if (local.ContainsKey(key)) {
return;
}
var changed = original.Add(key,value);
// The while loop condition will try assigning the changed dictionary
// back to the field. If it hasn't changed by another thread in the
// meantime, we assign the field and break out of the loop. But if another
// thread won the race (by changing the field while we were in an
// iteration of this loop), we'll loop and try again.
} while (Interlocked.CompareExchange(ref this.dict, changed, original) != original);
}
}
In realtà, io uso questo modello così spesso ho definito un metodo statico per questo scopo:
/// <summary>
/// Optimistically performs some value transformation based on some field and tries to apply it back to the field,
/// retrying as many times as necessary until no other thread is manipulating the same field.
/// </summary>
/// <typeparam name="T">The type of data.</typeparam>
/// <param name="hotLocation">The field that may be manipulated by multiple threads.</param>
/// <param name="applyChange">A function that receives the unchanged value and returns the changed value.</param>
public static bool ApplyChangeOptimistically<T>(ref T hotLocation, Func<T, T> applyChange) where T : class
{
Requires.NotNull(applyChange, "applyChange");
bool successful;
do
{
Thread.MemoryBarrier();
T oldValue = hotLocation;
T newValue = applyChange(oldValue);
if (Object.ReferenceEquals(oldValue, newValue))
{
// No change was actually required.
return false;
}
T actualOldValue = Interlocked.CompareExchange<T>(ref hotLocation, newValue, oldValue);
successful = Object.ReferenceEquals(oldValue, actualOldValue);
}
while (!successful);
Thread.MemoryBarrier();
return true;
}
tuo metodo Add allora diventa molto più semplice:
public class Something {
private ImmutableDictionary<string, string> dict = ImmutableDictionary<string, string>.Empty;
public void Add(string key, string value) {
ApplyChangeOptimistically(
ref this.dict,
d => d.ContainsKey(key) ? d : d.Add(key, value));
}
}
Sì, Credo che non sia infallibile nel modo in cui descrivi. Potrebbe essere necessario fare il proprio blocco intorno ad esso. –
Qual è lo scenario in cui sono presenti più thread che tentano di inserire lo stesso elemento? Se si intende parallelizzare il proprio lavoro, è necessario suddividere i dati su thread/macchine. –
Se l'assegnazione era '_dict [chiave] = valore;' - forse anche rimuovendo il controllo 'ContainsKey' - sarebbe thread-safe (?) –