c# - ¿Es seguro el hilo de la evaluación de la declaración de "cambio"?
thread-safety switch-statement (4)
Como ya asumió, la instrucción de cambio no es segura para subprocesos y puede fallar en ciertos escenarios.
Además, usar el lock
en su variable de instancia tampoco funcionará, ya que la declaración de lock
espera que un object
haga que su variable de instancia sea encuadrada. Cada vez que la variable de instancia está en un cuadro, se creará una nueva variable en cuadro, haciendo que el lock
efectivamente inútil.
En mi opinión tienes varias opciones para resolver este problema.
- Utilice el
lock
en una variable de instancia privada de cualquier tipo de referencia (elobject
hará el trabajo) - Use
ReaderWriterLockSlim
para permitir que varios subprocesos lean la variable de instancia, pero solo un subproceso escribe la variable de instancia a la vez. - Almacene atómicamente el valor de la variable de instancia en una variable local en su método (por ejemplo, usando
Interlocked.Read
oInterlocked.Exchange
) y realice elswitch
en la variable local. Tenga en cuenta que de esta manera podría usar el valor anterior para elswitch
. Debe decidir si esto puede causar problemas en su caso de uso concreto.
Considere el siguiente código de ejemplo:
class MyClass
{
public long x;
public void DoWork()
{
switch (x)
{
case 0xFF00000000L:
// do whatever...
break;
case 0xFFL:
// do whatever...
break;
default:
//notify that something going wrong
throw new Exception();
}
}
}
Olvídese de la inutilidad del fragmento: mi duda es sobre el comportamiento de la instrucción switch
.
Supongamos que el campo x
podría tener solo dos valores: 0xFF00000000L
o 0xFFL
. El interruptor de arriba no debe caer en la opción "por defecto".
Ahora imagine que un hilo está ejecutando el switch con "x" igual a 0xFFL, por lo tanto, la primera condición no coincidirá. Al mismo tiempo, otro hilo modifica la variable "x" a 0xFF00000000L. Sabemos que una operación de 64 bits no es atómica, por lo que la variable tendrá primero la palabra inferior puesta a cero, luego el conjunto superior después (o viceversa).
Si la segunda condición en el cambio se realizará cuando la "x" sea cero (es decir, durante la nueva asignación), ¿caeremos en el caso "predeterminado" no deseado?
En realidad estás publicando dos preguntas.
¿Es seguro para hilos?
Bueno, obviamente no lo es, otro hilo puede cambiar el valor de X mientras el primer hilo entra en el switch. Como no hay bloqueo y la variable no es volátil, cambiará en función del valor incorrecto.
¿Alguna vez golpearía el estado predeterminado del interruptor?
Teóricamente, puedes hacerlo, ya que la actualización de 64 bits no es una operación atómica y, por lo tanto, teóricamente puedes saltar a la mitad de la tarea y obtener un resultado combinado para x cuando lo indicas. Esto, estadísticamente, no ocurrirá a menudo, pero sucederá con el tiempo.
Pero el conmutador en sí mismo es seguro para subprocesos , lo que no es seguro para subprocesos se lee y escribe sobre variables de 64 bits (en un sistema operativo de 32 bits).
Imagina que en lugar de cambiar (x) tienes el siguiente código:
long myLocal = x;
switch(myLocal)
{
}
ahora el cambio se realiza sobre una variable local y, por lo tanto, es completamente seguro para subprocesos. El problema, por supuesto, está en la myLocal = x
y su conflicto con otras tareas.
La declaración de cambio de C # no se evalúa como una serie de condiciones si (como puede ser VB). C # construye efectivamente un hashtable de etiquetas para saltar en función del valor del objeto y salta directamente a la etiqueta correcta, en lugar de iterar a través de cada condición y evaluarla.
Esta es la razón por la que una declaración de cambio de C # no se deteriora en términos de velocidad a medida que aumenta el número de casos. Y también es la razón por la que C # es más restrictivo con lo que puede comparar en los casos de conmutador que con VB, en el que puede hacer rangos de valores, por ejemplo.
Por lo tanto, no tiene la condición de carrera potencial que ha indicado, dónde se realiza una comparación, el valor cambia, se realiza la segunda comparación, etc., porque solo se realiza una comprobación. En cuanto a si es totalmente seguro para subprocesos, no lo asumiría.
Haga una excavación con el reflector mirando a través de una instrucción de cambio de C # en IL y verá lo que está sucediendo. Compárelo con una instrucción de cambio de VB que incluye rangos en los valores y verá una diferencia.
Han pasado muchos años desde que lo miré, así que las cosas pueden haber cambiado un poco ...
Vea más detalles sobre el comportamiento de la instrucción de cambio aquí: ¿Hay alguna diferencia significativa entre usar if / else y switch-case en C #?
Sí, la propia instrucción de switch
, como se muestra en su pregunta, es segura para subprocesos. El valor del campo x
se carga una vez en una variable local (oculta) y ese local se usa para el bloque de switch
.
Lo que no es seguro es la carga inicial del campo x
en una variable local. No se garantiza que las lecturas de 64 bits sean atómicas, por lo que podría estar recibiendo lecturas obsoletas y / o desgarradas en ese momento. Esto podría resolverse fácilmente utilizando Interlocked.Read
, o similar, para leer explícitamente el valor del campo en el local de una manera segura para subprocesos:
long y = Interlocked.Read(ref x);
switch (y)
{
// ...
}