ternario significado reservadas palabras operador lenguaje identificadores float ejemplo corto claves c# .net multithreading locking thread-safety

c# - operador - palabras reservadas en cy su significado



¿Debo bloquear o marcar como volátil al acceder a una bandera booleana simple en C#? (5)

Digamos que tienes una operación simple que se ejecuta en un hilo de fondo. Desea proporcionar una forma de cancelar esta operación para crear una marca booleana que establezca en verdadero desde el controlador de eventos de clic de un botón de cancelación.

private bool _cancelled; private void CancelButton_Click(Object sender ClickEventArgs e) { _cancelled = true; }

Ahora está configurando el indicador de cancelación desde el hilo de la GUI, pero lo está leyendo desde el hilo de fondo. ¿Necesitas bloquear antes de acceder al bool?

¿Tendría que hacer esto (y, obviamente, bloquear el controlador de eventos con clic de botón también):

while(operationNotComplete) { // Do complex operation lock(_lockObject) { if(_cancelled) { break; } } }

O es aceptable hacer esto (sin bloqueo):

while(!_cancelled & operationNotComplete) { // Do complex operation }

O qué hay de marcar la variable _cancelled como volátil. ¿Es eso necesario?

[Sé que existe la clase BackgroundWorker con su método CancelAsync () incorporado, pero estoy interesado en la semántica y el uso del bloqueo y el acceso variable por hilos aquí, no la implementación específica, el código es solo un ejemplo.]

Parece que hay dos teorías.

1) Debido a que es un tipo integrado simple (y el acceso a los tipos incorporados es atómico en .net) y porque solo escribimos en él en un lugar y solo leemos en el hilo de fondo, no hay necesidad de bloquearlo o marcarlo como volátil.
2) Debería marcarlo como volátil porque, si no lo hace, el compilador puede optimizar la lectura en el ciclo while porque no cree que sea capaz de modificar el valor.

¿Cuál es la técnica correcta? (¿Y por qué?)

[Editar: Parece que hay dos escuelas de pensamiento claramente definidas y opuestas en esto. Estoy buscando una respuesta definitiva sobre este tema, así que si es posible publique sus motivos y cite sus fuentes junto con su respuesta.]


Busque Interlocked.Exchange() . Hace una copia muy rápida en una variable local que se puede utilizar para la comparación. Es más rápido que el bloqueo ().


El bloqueo no es necesario porque tiene un solo escenario de escritura y un campo booleano es una estructura simple sin riesgo de corromper el estado ( aunque fue posible obtener un valor booleano que no es falso ni verdadero ). Pero debe marcar el campo como volatile para evitar que el compilador realice algunas optimizaciones. Sin el modificador volatile el compilador podría almacenar en caché el valor en un registro durante la ejecución de su bucle en el subproceso de trabajo y, a su vez, el bucle nunca reconocería el valor cambiado. Este artículo de MSDN ( Cómo crear y terminar hilos (Guía de programación de C #) ) aborda este problema. Mientras que hay necesidad de bloqueo, un bloqueo tendrá el mismo efecto que marcar el campo volatile .


En primer lugar, el enhebrado es complicado;

Sí, a pesar de todos los rumores en contra, se requiere usar lock o volatile (pero no ambos) al acceder a un bool desde múltiples subprocesos.

Para tipos y accesos simples, como un indicador de salida ( bool ), entonces volatile es suficiente, esto garantiza que los subprocesos no almacenen el valor en sus registros (lo que significa que uno de los subprocesos nunca ve actualizaciones).

Para valores más grandes (donde la atomicidad es un problema), o donde desea sincronizar una secuencia de operaciones (un ejemplo típico es "si no existe y agrega" acceso al diccionario), un lock es más versátil. Esto actúa como una barrera de memoria, por lo que aún le brinda seguridad al subproceso, pero le brinda otras características como pulso / espera. Tenga en cuenta que no debe usar un lock en un tipo de valor o una string ; ni Type ni this ; La mejor opción es tener su propio objeto de bloqueo como campo ( readonly object syncLock = new object(); ) y bloquearlo.

Para ver un ejemplo de lo mal que se rompe (es decir, hacer un bucle para siempre) si no se sincroniza, consulte aquí .

Para abarcar varios programas, una primitiva de sistema operativo como Mutex o *ResetEvent también puede ser útil, pero esto es una exageración para un solo exe.


Para la sincronización de subprocesos, se recomienda que utilice una de las clases EventWaitHandle , como ManualResetEvent . Si bien es un poco más simple emplear una bandera booleana simple como lo hace aquí (y sí, querría marcarla como volatile ), es mejor comenzar con la práctica de usar las herramientas de subprocesos. Para tus propósitos, harías algo como esto ...

private System.Threading.ManualResetEvent threadStop; void StartThread() { // do your setup // instantiate it unset threadStop = new System.Threading.ManualResetEvent(false); // start the thread }

En tu hilo ..

while(!threadStop.WaitOne(0) && !operationComplete) { // work }

Luego en la GUI para cancelar ...

threadStop.Set();


_cancelled debe ser volatile . (Si no eliges bloquear)

Si un hilo cambia el valor de _cancelled , es posible que otros hilos no vean el resultado actualizado.

Además, creo que las operaciones de lectura / escritura de _cancelled son atómicas :

La sección 12.6.6 de la especificación de la CLI dice: "Una CLI conforme debe garantizar que el acceso de lectura y escritura a las ubicaciones de memoria correctamente alineadas que no sean mayores que el tamaño de la palabra nativa es atómico cuando todos los accesos de escritura a una ubicación tienen el mismo tamaño".