2009-08-03 9 views
39

Digamos que tiene una operación simple que se ejecuta en una cadena de fondo. Desea proporcionar una forma de cancelar esta operación para que cree un indicador booleano que establezca como verdadero desde el controlador de evento click de un botón cancelar.¿Debo bloquear o marcar como volátil cuando accedo a un indicador booleano simple en C#?

private bool _cancelled; 

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

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

se necesita para hacer esto (y, obviamente, bloquear en el botón de controlador de eventos click también):

while(operationNotComplete) 
{ 
    // Do complex operation 

    lock(_lockObject) 
    { 
     if(_cancelled) 
     { 
      break; 
     } 
    } 
} 

O es aceptable para hacer esto (sin bloqueo):

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

O qué tal marcar la variable cancelada como volátil. ¿Es eso necesario?

[Sé que existe la clase BackgroundWorker con su método incorporado Incorporated CancelAsync(), pero me interesa la semántica y el uso de acceso variable con bloqueo y subprocesos 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 simple incorporado (y el acceso a tipos incorporados es atómico en .net) y porque solo estamos escribiendo en un solo lugar y solo leyendo en el hilo de fondo no hay necesidad de bloquear o marca como volátil.
2) Debe 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 sobre esto. Estoy buscando una respuesta definitiva sobre esto así que, si es posible, publique sus razones y cite sus fuentes junto con su respuesta.]

+6

Sí, usted * necesita * ya sea 'volátil' o' lock' (para actuar como una barrera de memoria): vea http://stackoverflow.com/questions/458173/can-ac-thread-really-cache- a-value-and-ignore-changes-to-that-value-on-other-th/458193 # 458193 –

+1

Esto significa que Simon se va a quedar con la cara roja en el debate masivo que ha tenido con EFraim (http : //stackoverflow.com/questions/1221839/c-break-out-of-loop-on-button-press/1221854#1221854) – ThePower

+1

@Marc: Buen ejemplo, gracias. @ThePower: estoy contento de levantar las manos cuando no estoy seguro, así que espero escapar solo ligeramente rosada = :) –

Respuesta

31

En primer lugar, roscado es complicado ;-P

Sí, a pesar de todos los rumores en sentido contrario, se requiere paraya sea uso lockovolatile (pero no ambos) al acceder a un bool de múltiples hilos.

Para tipos simples y acceso tales como una bandera de salida (bool), entonces es suficiente volatile - esto asegura que las discusiones no conservan el valor en sus registros (es decir: uno de los hilos nunca ve las actualizaciones).

Para valores más grandes (donde la atomicidad es un problema), o en la que desea sincronizar una secuencia de operaciones (siendo un ejemplo típico "si no existe y añadir" el acceso diccionario), un lock es más versátil. Esto actúa como una barrera de memoria, por lo que aún le da la seguridad del hilo, pero proporciona otras funciones como pulso/espera. Tenga en cuenta que no debe usar un lock en un valor-tipo o un string; ni Type o 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, bucle siempre) si no se sincroniza - see here.

Para abarcar múltiples programas, una primitiva del sistema operativo como Mutex o *ResetEvent también puede ser útil, pero esto es exagerado para un solo exe.

+0

Creo que esto resuelve el problema.Un ejemplo reproducible de cómo puede salir mal si no usa bloqueo o volátil. –

+0

@ MarcGravell ♦ ¿hay alguna posibilidad de que 2 hilos escriban en la variable volátil al mismo tiempo? – onmyway133

+0

@entropy sí, no hay nada que detenga eso en absoluto. Uno de los valores ganará; no se define cual. Tenga en cuenta que el compilador solo le permite usar 'volátil' con tipos que pueden escribirse atómicamente, por lo que no terminará con un valor desgarrado –

6

_cancelled debe ser volatile. (si no elige 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ómica:

Sección 12.6.6 de los estados de especificaciones de la CLI: "Un CLI conforme deberá garantizar que de lectura y escritura a correctamente ubicaciones de memoria alineadas no más grandes que el tamaño de palabra nativo es atómico cuando todos los accesos de escritura a una ubicación son del mismo tamaño. "

+0

@Mitch: pero en este caso esto significa que puede arruinar las cosas. – EFraim

+0

Al igual que un punto de corrección, el bloqueo no tendrá efecto si se almacena en caché una lectura o escritura en una variable. El bloqueo NO es un reemplazo de una variable volátil. –

+0

atomicity tiene poco que ver con eso ... si el otro hilo aún está masticando sus propios registros, entonces no hay diferencia si se escribe el valor en 1 parte o en 26. –

2

Para la sincronización de hilos, se recomienda utilizar una de las clases EventWaitHandle, como ManualResetEvent. Si bien es marginalmente más simple emplear un indicador booleano simple como lo hace aquí (y sí, querría marcarlo como volatile), IMO es mejor usar la herramienta de enhebrar.Para sus propósitos, que haría 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 el hilo ..

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

Luego, en la interfaz gráfica de usuario para cancelar ...

threadStop.Set(); 
+0

Una primitiva del sistema operativo suele ser excesiva para una sola aplicación, IMO. La mayoría de las cosas se pueden hacer con solo un 'Monitor'. –

+0

@Marc: Si bien * ResetEvent no se requiere explícitamente aquí, no creo que esté dispuesto a decir que es excesivo para una sola aplicación. Hay muchas instancias donde múltiples hilos dentro de una aplicación pueden necesitar usar la funcionalidad de un EventWaitHandle como ManualResetEvent. –

1

Look hasta Interlocked.Exchange(). Hace una copia muy rápida en una variable local que se puede usar para comparar. Es más rápido que lock().

+0

No son las consideraciones de velocidad; es la corrección ... –

+0

+1, no siempre es la mejor, pero es una de las tres posibilidades (bloqueo/enclavamiento/volatilidad). –

+0

Interlocked.Exchange() no sería mi primera opción para una variable utilizada para controlar un ciclo. Yo usaría un Evento/WaitHandle. Si necesita obtener una variable entera/booleana atómicamente, es una buena opción. – hughdbrown

5

No es necesario bloquear porque tiene un escenario de escritor único y un campo booleano es una estructura simple sin riesgo de corromper el estado (while it was possible to get a boolean value that is neither false nor true). 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 de un registro durante la ejecución de su bucle en su hilo de trabajo y, a su vez, el bucle nunca reconocería el valor modificado. Este artículo de MSDN (How to: Create and Terminate Threads (C# Programming Guide)) soluciona este problema. Si bien es necesario bloquear, un bloqueo tendrá el mismo efecto que marcar el campo volatile.

+0

Eso es correcto. Como lees o estableces el valor de esta bandera, todo lo que necesitas es un atributo 'volátil '. – jpbochi

+0

Pero en teoría podría tener un escenario bastante weired si la actualización del campo booleano no es atómica y una actualización parcial da como resultado un valor que no es ni verdadero ni falso, pero esto no causaría problemas en el escenario de uso dado. –

+0

@Daniel: una actualización bool está garantizada por la especificación atómica. –

Cuestiones relacionadas