supongo que podría ser evitar una condición de carrera en la que si usted tiene:
dictionary[i] = dictionary[i] + 1
No es atómica. El dictionary
que se le asignó podría cambiar después de, obtuvo el valor y se incrementó.
Imagínese este código:
public Dictionary<int, int> dictionary = new Dictionary<int, int>();
public void Increment()
{
int newValue = dictionary[0] + 1;
//meanwhile, right now in another thread: dictionary = new Dictionary<int, int>();
dictionary[0] = newValue; //at this point, "dictionary" is actually pointing to a whole new instance
}
Con la asignación de variables locales que tienen, se parece más a esto para evitar la condición:
public void IncrementFix()
{
var dictionary2 = dictionary;
//in another thread: dictionary = new Dictionary<int, int>();
//this is OK, dictionary2 is still pointing to the ORIGINAL dictionary
int newValue = dictionary2[0] + 1;
dictionary2[0] = newValue;
}
Tenga en cuenta que no satisface plenamente todas hilo -los requisitos de seguridad. Por ejemplo, en este caso, comenzamos a incrementar el valor, pero la referencia dictionary
en la clase ha cambiado a una instancia completamente nueva. Pero si necesita ese nivel más alto de seguridad de subprocesos, entonces necesita implementar su propia sincronización/bloqueo agresivo, que generalmente está fuera del alcance de las optimizaciones del compilador. Sin embargo, este, por lo que puedo decir, realmente no agrega ningún gran golpe (si lo hay) al procesamiento y evita esta condición. Esto podría ser especialmente el caso si dictionary
es una propiedad no es un campo como lo es en su ejemplo, en cuyo caso definitivamente sería una optimización para no resolver el getter de propiedad dos veces. (Por casualidad, ¿su código real usa una propiedad para el diccionario en lugar del campo que publicó?)
EDIT: Bueno, por un método simple:
public void IncrementDictionary()
{
dictionary[0]++;
}
La IL informó de LINQPad es:
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldfld UserQuery.dictionary
IL_0007: dup
IL_0008: stloc.0
IL_0009: ldc.i4.0
IL_000A: ldloc.0
IL_000B: ldc.i4.0
IL_000C: callvirt System.Collections.Generic.Dictionary<System.Int32,System.Int32>.get_Item
IL_0011: ldc.i4.1
IL_0012: add
IL_0013: callvirt System.Collections.Generic.Dictionary<System.Int32,System.Int32>.set_Item
IL_0018: nop
IL_0019: ret
No estoy del todo seguro (no soy un mago de la IL) , pero creo que la llamada a dup
básicamente duplica la misma referencia de diccionario en la pila, por lo tanto, independientemente de que las llamadas de obtención y establecimiento apunten al mismo diccionario. Tal vez así es como ILSpy lo representa como código C# (es más o menos lo mismo al menos como lo hace el comportamiento). Creo. Por favor, corrígeme si me equivoco porque, como dije, todavía no conozco a IL como la palma de mi mano.
EDITAR: Tengo que ejecutar, pero la esencia es: ++
y +=
no son operaciones atómicas y en realidad es mucho más complicado en las instrucciones ejecutadas que las que se muestran en C#. Como tal, para asegurarse de que cada uno de los pasos get/increment/set se realiza en la misma instancia de diccionario (como era de esperar y requiere del código C#) se hace una referencia local al diccionario para evitar ejecutar el campo " obtener "operación dos veces" lo que podría dar como resultado apuntar a una nueva instancia. Cómo ILSpy muestra que todo el trabajo relacionado con una operación indexada + = está a la altura.
Nota importante: no se crea un segundo diccionario. Por el contrario, se toma una segunda referencia al diccionario existente. – Jon
@Jon: Correcto, mi error. –
El ILSpy que publicó no es una representación exacta de lo que está sucediendo en el código CIL directamente. Es posible que esto sea un error o imperfección en el decompilador CIL-to-C#. ¿Puedes publicar el CIL para que podamos estar seguros? – Dai