2011-09-27 14 views
5

He aquí un ejemplo:¿Cómo lidiar con las condiciones de carrera en multi-threading?

if (control.InvokeRequired) 
{ 
    control.BeginInvoke(action, control); 
} 
else 
{ 
    action(control); 
} 

¿Y si entre el estado y la BeginInvoke llamar el control está dispuesta, por ejemplo?

Otro ejemplo que tiene que ver con los acontecimientos:

var handler = MyEvent; 

if (handler != null) 
{ 
    handler.BeginInvoke(null, EventArgs.Empty, null, null); 
} 

Si MyEvent es dado de baja entre la primera línea y la sentencia if, si el todavía será ejecutado comunicado. Sin embargo, ¿es ese diseño apropiado? ¿Qué pasa si con la desuscripción también se produce la destrucción del estado necesaria para la invocación adecuada del evento? Esta solución no solo tiene más líneas de código (repetitivo), sino que no es tan intuitiva y puede generar resultados inesperados para el cliente.

¿Qué dices, SO?

+0

la razón por la que existe el patrón descrito anteriormente para controladores de eventos es mantener viva una referencia al controlador para que no se pueda eliminar. –

+0

@Mitch Wheat: Sí, no estoy diciendo que el controlador necesariamente se elimine, pero que si el cliente cancela la suscripción del evento, también puede decidir que ya no necesita algún tipo de objeto de estado que normalmente solo utiliza el controlador de eventos. Debido a que el evento aún se ejecuta después de anular la suscripción con un momento desafortunado, puede haber un error muy difícil de rastrear porque el resultado esperado fue que el controlador no se ejecutará después de anular la suscripción. –

+0

@Mitch Vea mi respuesta. –

Respuesta

5

En mi opinión, si esto es un problema, tanto la administración de subprocesos como la administración del ciclo de vida de los objetos son imprudentes y deben volver a examinarse.

En el primer ejemplo, el código no es simétrico: BeginInvoke no va a esperar a que action se complete, pero la llamada directa lo hará; esto es probablemente un error ya.

Si espera que otro hilo potencialmente elimine el control con el que está trabajando, no tiene más remedio que atrapar el ObjectDisposedException - y no puede lanzarse hasta que ya esté dentro de action, y posiblemente no en el hilo actual gracias a BeginInvoke.

No es correcto suponer que una vez que se haya anulado la suscripción a un evento, ya no recibirá más notificaciones. Ni siquiera requiere varios hilos para que esto suceda, solo múltiples suscriptores.Si el primer suscriptor hace algo mientras procesa una notificación que hace que el segundo suscriptor anule la suscripción, la notificación actualmente "en vuelo" irá al segundo suscriptor. Es posible que pueda mitigar esto con una cláusula de guardia en la parte superior de la rutina de su controlador de eventos, pero no puede evitar que suceda.

+0

Me gustaría ver cuidadosamente el código que permitió a un suscriptor causar un evento que cancela la suscripción de un segundo suscriptor al mismo evento –

+0

+1: las colas de mensajes garantizan la entrega, no la no entrega. – SingleNegationElimination

3

Hay algunas técnicas para resolver una condición de carrera:

  • abrigo de todo el asunto con un mutex. Asegúrate de que haya un bloqueo que cada hilo debe adquirir primero antes de que pueda comenzar a correr en la carrera. De esta forma, tan pronto como obtenga el bloqueo, sabrá que ningún otro hilo está usando ese recurso y puede completarlo de manera segura.
  • Encuentra una manera de detectar y recuperar de ellos; Esto puede ser muy complicado, pero algunos tipos de aplicaciones funcionan bien; Una forma típica de lidiar con esto es mantener un contador de la cantidad de veces que un recurso ha cambiado; Si termina con una tarea y descubre que el número de versión es diferente de cuando comenzó, lea la nueva versión y comience la tarea desde el principio (o simplemente falle)
  • rediseñe la aplicación para usar solo acciones atómicas; básicamente, esto significa usar operaciones que se pueden completar en un solo paso; esto a menudo implica operaciones de "comparar y cambiar", o ajustar todos los datos de la transacción en un solo bloque de disco que se puede escribir atómicamente.
  • rediseña la aplicación para usar técnicas de bloqueo; Esta opción solo tiene sentido cuando la satisfacción de restricciones duras en tiempo real es más importante que el servicio de cada solicitud, porque los diseños sin bloqueo pierden datos inherentemente (generalmente de naturaleza de baja prioridad).

La opción "correcta" depende de la aplicación. Cada opción tiene compensaciones de rendimiento que pueden hacer que el beneficio de la concurrencia sea menos atractivo.

-1

Si este comportamiento se está extendiendo varios lugares en su aplicación, podría merecer volver a diseñar la API, que se parece:

if(!control.invokeIfRequired()){ 
    action(action); 
} 

Sólo la misma idea que la biblioteca estándar JDK ConcurrentHashMap.putIfAbsent(...). Por supuesto, debe tratar la sincronización dentro de este nuevo método control.invokeIfRequired().

+0

Gracias, pero ya es parte de un método de extensión :) –

+0

El segundo ejemplo es bastante interesante, ya que es posible que 'handler' se pueda asignar nulo por otros hilos. Necesitamos un objeto de bloqueo aquí, por lo que cada lectura y escritura en 'handler' está protegida por el mismo bloqueo. No estoy seguro de C#. Pero en Java, el código de trabajo podría tener el siguiente aspecto: 'synchronized (lockObj) {if (handler! = Null) {handler.beginInvoke (...)}} –

+1

-1 Esta sugerencia, si bien es útil, no es una respuesta a la pregunta. –

Cuestiones relacionadas