2012-01-28 11 views
6

Tengo un problema con el subprocesamiento múltiple en C#. Utilizo un evento para actualizar una etiqueta en un formulario de otro hilo, para lo cual necesito usar el comando Invoke() por supuesto. Esa parte también funciona bien. Sin embargo, el usuario puede cerrar el formulario y aquí el programa puede bloquearse si el evento se envía en un momento desafortunado.'Interbloqueo' con solo un objeto bloqueado?

Así que, pensé que simplemente anularía el método Dispose() del formulario, establecería un booleano en verdadero dentro del código bloqueado, y también verificaría ese booleano e invocaría el evento en código bloqueado.

Sin embargo, cada vez que cierro el formulario el programa se congela por completo.

Estas son las partes mencionadas del código:

private object dispose_lock = new object(); 
private bool _disposed = false; 

private void update(object sender, EventArgs e) 
{ 
    if (InvokeRequired) 
    { 
     EventHandler handler = new EventHandler(update); 
     lock (dispose_lock) 
     { 
      if (_disposed) return; 
      Invoke(handler); // this is where it crashes without using the lock 
     } 
     return; 
    } 

    label.Text = "blah"; 
} 

protected override void Dispose(bool disposing) 
{ 
    eventfullObject.OnUpdate -= update; 
    lock (dispose_lock) // this is where it seems to freeze 
    { 
     _disposed = true; // this is never called 
    } 
    base.Dispose(disposing); 
} 

espero que alguien aquí tiene alguna idea de lo que está mal con este código. ¡Gracias de antemano!

+0

Podría una llamada de actualización en la aplicación real que la ventana de disponer? En ese caso, el hilo de fondo podría tener un bloqueo, y el hilo de la interfaz de usuario podría terminar en Deshacer el bloqueo en el mismo objeto que el hilo de fondo está reteniendo. –

+1

Dónde obtiene la variable InvokeRequired, debe invocarse sobre el control que desea actualizar, es decir: if (label.InvokeRequired) {//} – Lloyd

Respuesta

1

Realmente iría simple aquí. En lugar de implementar un código de seguridad de subprocesos complicado, simplemente captaría la excepción y no haré nada si falla.

Asumiendo que es una ObjectDisposedException:

try 
{ 
    this.Invoke(Invoke(handler)); 
} 
catch (ObjectDisposedException) 
{ 
    // Won't do anything here as 
    // the object is not in the good state (diposed when closed) 
    // so we can't invoke. 
} 

Es más simple y bastante sencillo. Si un comentario especifica por qué detecta la excepción, creo que está bien.

+1

Mala idea IMO ... – CodesInChaos

+0

@CodeInChaos No creo la complejidad del bloqueo, anulación de Dispose ... etc. es mejor que simplemente atrapar la excepción. Tal vez podrías explicar ** por qué ** crees que es una mala idea ... – ken2k

+0

La misma opinión aquí: http://stackoverflow.com/a/1874785/870604 – ken2k

6

Lo que no se tiene en cuenta es que el delegado pasado al Invoke se llama asincrónicamente en el subproceso de la interfaz de usuario. Llamar al Invoke publica un mensaje en la cola de mensajes de formularios y se retoma un tiempo después.

Lo que sucede no es:

UI Thread     Background Thread 
          Call update() 
          take lock 
          Call Invoke() 
Call update()    
          release lock 
Call Dispose() 
take lock 
release lock 

Pero en su lugar:

UI Thread     Background Thread 
          Call update() 
           take lock 
           Call Invoke() 
           block until UI Thread processes the message 
Process messages 
... 
Dispose() 
    wait for lock ****** Deadlock! ***** 
... 
Call update()    
          release lock 

Debido a esto, el hilo de fondo puede sostener que mantiene el bloqueo, mientras que el hilo de interfaz de usuario está intentando ejecutar Dispose

La solución es mucho más simple que lo que probaste. Debido a que Invoke se publica de forma asincrónica, no hay necesidad de un bloqueo.

private bool _disposed = false; 

private void update(object sender, EventArgs e) 
{ 
    if (InvokeRequired) 
    { 
     EventHandler handler = new EventHandler(update); 
     Invoke(handler); 
     return; 
    } 

    if (_disposed) return; 

    label.Text = "blah"; 
} 

protected override void Dispose(bool disposing) 
{ 
    eventfullObject.OnUpdate -= update; 
    _disposed = true; // this is never called 
    base.Dispose(disposing); 
} 

La bandera _disposed solo se lee o escribe en el hilo de la interfaz de usuario por lo que no es necesario bloquearla. Ahora se llama pila se ve así:

UI Thread     Background Thread 
          Call update() 
           take lock 
           Call Invoke() 
           block until UI Thread processes the message 
Process messages 
... 
Dispose() 
    _disposed = true; 
... 

Call update() 
    _disposed is true so do nothing    
+1

La llamada 'Invoke' siempre puede fallar. El código seguirá causando una excepción no controlada cuando se invoque Invoke en el momento preciso en que se está eliminando el Control. – JaredPar

+0

Invoke siempre es sincrónico. – usr

+0

@usr Lo que estaba tratando de describir es que Invoke es asincrónico con respecto a la cadena de interfaz de usuario. Es sincrónico con respecto al hilo de fondo. Trataré de aclarar mi respuesta – shf301

0

OMI Dispose es demasiado tarde ...

Yo recomendaría poner un poco de código en FormClosing que se llama antes de Dispose sucede que yo sepa.

Para este tipo de casos, generalmente tiendo a usar un patrón diferente (atómico) para su verificación, por ejemplo, a través de la clase Interlocked.

private long _runnable = 1; 

private void update(object sender, EventArgs e) 
{ 
    if (InvokeRequired) 
    { 
     EventHandler handler = new EventHandler(update); 
     if (Interlocked.Read (ref _runnable) == 1) Invoke(handler); 
     return; 
    } 

    label.Text = "blah"; 
} 

En FormClosing que acaba de llamar Interlocked.Increment (ref _runnable).

0

Solo en caso de que ninguna de las otras respuestas sea la culpable, ¿hay otro código que finalice el hilo que no se publica? Estoy pensando que podría estar utilizando hilos lisos y no un BackgroundWorker, y podría haber olvidado para establecer Thread.isBackround a cierto

1

Uno de los peligros de usar Control.Invoke es que podría estar dispuesto sobre el hilo de interfaz de usuario en un momento lamentable, ya sugeriste. La forma más común ocurre esto es cuando se tiene el siguiente orden de eventos

  1. subproceso de fondo: en cola una llamada de vuelta con invocación
  2. Tema primer plano: Desechar el control en el que el fondo denominado Invoke
  3. Tema primer plano : Dequeues la devolución de llamada en un control eliminado

En este escenario, la invocación fallará y provocará una excepción en el hilo de fondo. Es probable que esto sea lo que estaba causando que tu aplicación fallara en primer lugar.

Con el nuevo código, aunque esto causa un bloqueo muerto. El código tomará el bloqueo en el paso # 1. Luego, el desecho ocurre en la UI en el paso n. ° 2 y está esperando el bloqueo que no se liberará hasta que se complete el paso n. ° 3.

La forma más fácil para hacer frente a este problema es aceptar que Invoke es una operación que se puede y se producirá un error, por lo tanto necesita un try/catch

private void update(object sender, EventArgs e) 
{ 
    if (InvokeRequired) 
    { 
     EventHandler handler = new EventHandler(update); 
     try 
     { 
      Invoke(handler); 
     } 
     catch (Exception) 
     { 
      // Control disposed while invoking. Nothing to do 
     } 
     return; 
    } 

    label.Text = "blah"; 
} 
1

¿Por qué no sólo tiene que utilizar BeginInvoke en lugar de invocación - este won Bloquea el hilo de fondo. No parece que haya alguna razón específica por la cual el hilo de fondo tiene que esperar a la actualización de interfaz de usuario que se produzca a partir de lo que han mostrado

1

surge Otro escenario de interbloqueo al llamar Dispatcher.Invoke (en una aplicación de WPF) o Control.Invocar (en una aplicación de Windows Forms) mientras está en posesión de un candado. Si la interfaz de usuario está ejecutando otro método que está esperando en el mismo bloqueo, se producirá un interbloqueo allí . Esto a menudo se puede solucionar simplemente llamando a BeginInvoke en su lugar de Invoke. Alternativamente, puede soltar su bloqueo antes de llamar al Invocar, aunque esto no funcionará si la persona que llama sacó el bloqueo. Nos explicamos Invoke y BeginInvoke en Rich Client Applications and Thread Affinity.

fuente: http://www.albahari.com/threading/part2.aspx

Cuestiones relacionadas