c# - una - ¿Cómo es BackgroundWorker.CancellationPending thread-safe?
ui thread c# (4)
La forma de cancelar la operación de BackgroundWorker es llamar a BackgroundWorker.CancelAsync()
:
// RUNNING IN UI THREAD
private void cancelButton_Click(object sender, EventArgs e)
{
backgroundWorker.CancelAsync();
}
En un controlador de eventos BackgroundWorker.DoWork, verificamos BackgroundWorker.CancellationPending
:
// RUNNING IN WORKER THREAD
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
while (!backgroundWorker.CancellationPending) {
DoSomething();
}
}
La idea anterior está en toda la web, incluida la página de MSDN para BackgroundWorker .
Ahora, mi pregunta es esta: ¿Cómo diablos es este hilo seguro?
He examinado la clase BackgroundWorker en ILSpy - CancelAsync()
simplemente establece cancelaciónPendiente en verdadero sin usar una barrera de memoria, y CancellationPending
simplemente devuelve cancelaciónPendiente sin usar una barrera de memoria.
Según esta página de Jon Skeet , lo anterior no es seguro para subprocesos. Pero la documentación para BackgroundWorker.CancellationPending dice: "Esta propiedad está destinada para el uso del subproceso de trabajo, que debe verificar periódicamente el valor de Cancelación pendiente y abortar la operación en segundo plano cuando se establece en verdadero".
¿Que está pasando aqui? ¿Es seguro para subprocesos o no?
Es seguro para subprocesos.
El código
while (!backgroundWorker.CancellationPending)
está leyendo una propiedad y el compilador sabe que no puede almacenar en caché el resultado.
Y como en el marco normal, cada escritura es igual que VolatileWrite, el método CancelAsync () solo puede establecer un campo.
La pregunta puede ser interpretada de dos maneras:
1) ¿Es correcta la implementación de BackgroundWorker.CancellationPending?
No es correcto porque puede hacer que la solicitud de cancelación pase desapercibida. La implementación utiliza la lectura ordinaria del campo de respaldo. Si otros subprocesos actualizan este campo de respaldo, la actualización puede ser invisible para el código de lectura. Así es como se ve la implementación:
// non volatile variable:
private Boolean cancellationPending;
public Boolean CancellationPending {
get {
// ordinary read:
return cancellationPending;
}
}
La implementación correcta intentaría leer el valor más actualizado. Esto se puede lograr declarando el campo de respaldo ''volátil'', usando la barrera de memoria o el bloqueo. Probablemente hay otras opciones y algunas de ellas son mejores que otras, pero depende del equipo que posee la clase "BackgroundWorker".
2) ¿Es correcto el código que usa BackgroundWorker.CancellationPending?
while (!backgroundWorker.CancellationPending) {
DoSomething();
}
Este código es correcto. El bucle girará hasta que Cancelación pendiente devuelva "verdadero". Teniendo en cuenta que las propiedades de C # es solo una sintaxis de azúcar para los métodos CLR. En el nivel de IL, este es solo otro método que se verá como "get_CancellationPending". Los valores de retorno del método no se almacenan en la memoria caché llamando al código (probablemente porque calcular si el método tiene efectos secundarios es demasiado difícil).
Se debe a que BackgroundWorker hereda de un componente que hereda de MarshalByRefObject. Un objeto MBRO puede residir en otra máquina o en otro proceso o dominio de aplicación. Esto funciona al hacer que el objeto sea suplantado por un proxy que tiene exactamente las mismas propiedades y métodos, pero cuyas implementaciones distribuyen la llamada a través del cable.
Un efecto colateral de eso es que el jitter no puede en línea los métodos y las propiedades, que romperían el proxy. Lo que también evita que se realicen optimizaciones que almacenen el valor del campo en un registro de la CPU. Al igual que lo hace volátil.
Un buen punto y una duda muy justa. Suena como si no fuera seguro. Creo que MSDN también tiene la misma duda y es por eso que está bajo el método BackgroundWorker.CancelAsync .
Precaución
Tenga en cuenta que su código en el controlador de eventos DoWork puede terminar su trabajo mientras se realiza una solicitud de cancelación, y su círculo de sondeo puede perder el hecho de que Cancelación pendiente se establece en verdadero. En este caso, el indicador Cancelado de System.ComponentModel.RunWorkerCompletedEventArgs en su controlador de eventos RunWorkerCompleted no se establecerá en verdadero, incluso si se realizó una solicitud de cancelación. Esta situación se denomina condición de carrera y es una preocupación común en la programación multiproceso. Para obtener más información sobre problemas de diseño de subprocesos múltiples, consulte Mejores prácticas de subprocesos administrados.
No estoy seguro de la implementación de la clase BackgroundWorker. En este caso, es importante observar cómo utiliza internamente la propiedad BackgroundWorker.WorkerSupportsCancellation .