Aunque me gusta la documentación, que tienden a verificar las cosas con un pequeño programa en caso de duda o siento que podría estar asumiendo demasiado.
El siguiente código verifica que, de hecho, puede enumerar la colección de valores de forma segura al agregar o eliminar claves de un hilo separado al que está teniendo lugar la enumeración. Esto no causará que la colección habitual fuera excepciones modificadas. Con más detalle, aquí hay un par de casos de prueba
Caso 1: Enumeración Valores y eliminación de una clave
Si sigue la siguiente secuencia:
- iniciar la enumeración de la colección de valores de una thread
- eliminar una clave de un hilo diferente que no hemos enumerado todavía
- Continuar enumerando en el subproceso original
El comportamiento observado es que la clave eliminada se enumerará ya que existía en la colección de valores cuando comenzamos la enumeración. No se levantará ninguna excepción.
Caso 2: Enumeración Valores y la adición de una clave
- iniciar la enumeración de la colección de valores de un hilo
- agregar una nueva clave de un hilo diferente que no hemos enumerado todavía
- Continuar enumerar en el subproceso original
El comportamiento observado es que la clave agregada no se enumerará ya que d id no existe en la recopilación de valores cuando comenzamos a enumerarlo. No se planteará ninguna excepción ya sea que usemos TryAdd o agreguemos asignando directamente al diccionario, es decir, diccionario [clave] = valor.
Código de ejemplo
Este es el programa ejemplo que muestra dos casos:
ConcurrentDictionary<int, int> dictionary = new ConcurrentDictionary<int, int>();
// Seed the dictionary with some arbitrary values;
for (int i = 0; i < 30; i++)
{
dictionary.TryAdd(i, i);
}
// Reader thread - Enumerate the Values collection
Task.Factory.StartNew(
() =>
{
foreach (var item in dictionary.Values)
{
Console.WriteLine("Item {0}: count: {1}", item, dictionary.Count);
Thread.Sleep(20);
}
}
);
// writer thread - Modify dictionary by adding new items and removing existing ones from the end
Task.Factory.StartNew(
() =>
{
for (int i = 29; i >= 0; i--)
{
Thread.Sleep(10);
//Remove an existing entry
int removedValue;
if (dictionary.TryRemove(i, out removedValue))
Console.WriteLine("Removed item {0}", removedValue);
else
Console.WriteLine("Did not remove item {0}", i);
int iVal = 50 + i*2;
dictionary[iVal] = iVal;
Thread.Sleep(10);
iVal++;
dictionary.TryAdd(iVal, iVal);
}
}
);
Console.ReadKey();
Y aquí está la salida en modo de lanzamiento:
El empadronador se comporta de forma diferente a las claves y valores pr operties. Si bien estos brindan una instantánea del diccionario en un momento dado, los contenidos devueltos por ['GetEnumerator'] (https://msdn.microsoft.com/en-us/library/dd287131.aspx) (que también se usan en Las consultas LINQ que utilizan el diccionario como fuente) contienen modificaciones realizadas en el diccionario después de que se llamó a 'GetEnumerator'. –