2010-09-04 10 views
5

Tengo una aplicación que inicia System.Threading.Timer, luego este temporizador cada 5 segundos lee información de una base de datos vinculada y actualiza GUI en la forma principal de aplicación;Objeto Dispuesto aplicación de excepción y multi hilo

Desde el System.Threading.Timer crear otro hilo para el evento Tick, necesito utilizar Object.Invoke para la actualización de la interfaz de usuario en el formulario principal de la aplicación con el código como el siguiente:

this.Invoke((MethodInvoker)delegate() 
    { 
     label1.Text = "Example"; 
    }); 

La aplicación funcionan muy bien, pero a veces, cuando el usuario cierra el formulario principal y luego cierra la aplicación, si el segundo hilo en el evento timer_tick está actualizando la interfaz de usuario en el hilo principal, el usuario obtiene una ObjectDisposedException.

¿Cómo puedo hacer para detener y cerrar el temporizador de enhebrado antes de cerrar el formulario principal y evitar la excepción de objeto eliminado?

+0

'System.Threading.Timer' no tiene un evento' Tick'. El único temporizador de BCL que realmente tiene el evento 'Tick' es' System.Windows.Forms.Timer'. ¿Puedes aclarar cuál estás usando? Esto es importante. –

Respuesta

3

System.Timers.Timer es una clase horrible. No hay una buena manera de detenerlo de manera confiable, siempre hay una carrera y no se puede evitar. El problema es que su evento Elapsed se genera a partir de una cadena de subprocesos. No puede predecir cuándo ese hilo realmente comienza a ejecutarse. Cuando llama al método Stop(), es posible que ese subproceso ya se haya agregado al grupo de subprocesos pero todavía no se haya ejecutado. Está sujeto tanto al programador de subprocesos de Windows como al programador de subprocesos de subprocesos.

Ni siquiera puede resolverlo de forma fiable mediante el retraso arbitrario del cierre de la ventana.El programador de subprocesos puede retrasar la ejecución de un subproceso hasta en 125 segundos en los casos más extremos. Reducirá la probabilidad de una excepción al retrasar el cierre en un par de segundos, no será cero. Retrasar el cierre por 2 minutos no es realista.

Simplemente no lo use. Utilice System.Threading.Timer y conviértalo en un temporizador de una sola operación que reiniciará en el controlador de eventos. O use System.Windows.Forms.Timer, es sincrónico.

Un temporizador WF debe ser su elección aquí porque utiliza Control.Invoke(). El objetivo de delegado no comenzará a ejecutarse hasta que su subproceso de interfaz de usuario permanezca inactivo. El mismo comportamiento exacto que obtendrás de un temporizador WF.

+0

¿Qué es el temporizador WF? es igual a DispatcherTimer? En realidad estoy usando System.Threading.Timer y no System.Timers.Timer. – aleroot

+0

Windows Forms Timer. Sí, lo mismo que el WPF DispatcherTimer. Expliqué cómo usar un System.Threading.Timer, establecí el período en 0. –

+0

Ahora comienzo System.Threading.Timer así: System.Threading.TimerCallback oCallbackGrid = new System.Threading.TimerCallback (GridTimer_Tick); timerRefreshGrid = new System.Threading.Timer (oCallbackGrid, null, 1000, 3000); porque necesito disparar GridTimer_Tick cada 3 segundos. ¿Cómo puedo hacer lo contrario? – aleroot

7

Esto es un poco de una proposición difícil ya que debe asegurarse de lo siguiente en un caso dado Cerrar

  1. El temporizador se detiene. Esto es bastante sencillo
  2. El control que se está actualizando no se elimina cuando se ejecuta el delegado. De nuevo directo.
  3. Se ha completado el código que se está ejecutando en ese momento. Esto es más difícil pero factible
  4. No hay métodos Invoke pendientes. Esto es bastante más difícil de lograr

Me he encontrado con este problema antes y he descubierto que prevenir este problema es muy problemático e implica un montón de código complicado y difícil de mantener. En cambio, es mucho más fácil captar las excepciones que pueden surgir de esta situación. Normalmente lo hago envolviendo el método de invocación de la siguiente manera

static void Invoke(ISynchronizedInvoke invoke, MethodInvoker del) { 
    try { 
    invoke.Invoke(del,null); 
    } catch (ObjectDisposedException) { 
    // Ignore. Control is disposed cannot update the UI. 
    } 
} 

No hay nada inherentemente malo en ignorar esta excepción si se siente cómodo con las consecuencias. Eso es si está cómodo con la IU que no se actualiza después de que ya se ha eliminado. Ciertamente estoy :)

Lo anterior no soluciona el problema n. ° 2 y aún debe ser hecho manualmente en su delegado. Cuando trabajo con WinForms, a menudo uso la siguiente sobrecarga para eliminar esa verificación manual también.

static void InvokeControlUpdate(Control control, MethodInvoker del) { 
    MethodInvoker wrapper =() => { 
    if (!control.IsDisposed) { 
     del(); 
    } 
    }; 
    try { 
    control.Invoke(wrapper,null); 
    } catch (ObjectDisposedException) { 
    // Ignore. Control is disposed cannot update the UI. 
    } 
} 

Nota

como Hans observó ObjectDisposedException no es la única excepción que se puede subir desde el método Invoke. Hay varios otros, incluyendo al menos InvalidOperationException que debe considerar manejar.

+0

+1: ¿Tengo razón al pensar que, para el segundo ejemplo, si suponemos que el único hilo que va a deshacerse de un objeto UI es el hilo UI (que * debería * ser el caso), nunca debería ver un ' ObjectDisposedException', ya que la verificación de 'control.IsDisposed' se lleva a cabo en el subproceso de interfaz de usuario? –

+0

Esa no es la única excepción que puede surgir, también obtendrá InvalidOperationException. –

+0

@Hans, se olvidó de eso, agregó una nota. – JaredPar

1

Crea dos booleanos llamados 'StopTimer' y 'TimerStopped'. Establezca la propiedad AutoReset del temporizador en falso. A continuación, formatee el método transcurrido a lo siguiente:

TimerStopped = false; 
Invoke((MethodInvoker)delegate { 
    // Work to do here. 
}); 
if (!StopTimer) 
    timer.Start(); 
else 
    TimerStopped = true; 

esta manera que están impidiendo una condición de carrera, comprobar si el temporizador debe seguir e informar cuando el método ha llegado a su fin.

Ahora formato a su evento FormClosing de la siguiente manera:

if (!TimerStopped) 
{ 
    StopTimer = true; 
    Thread waiter = new Thread(new ThreadStart(delegate { 
     while (!TimerStopped) { } 
     Invoke((MethodInvoker)delegate { Close(); }); 
    })); 
    waiter.Start(); 
    e.Cancel = true; 
} 
else 
    timer.Dispose(); 

Si el temporizador no ha parado todavía, se puso en marcha un hilo que esperar hasta que se ha hecho y luego tratar de cerrar el formulario de nuevo.

Cuestiones relacionadas