2011-11-22 16 views
5

Duplicar posible:
Timer, event and garbage collection : am I missing something?¿Por qué el temporizador local capturado no saldrá del alcance?

private void TrayForm_Load(object sender, EventArgs e) 
{ 
    System.Timers.Timer HideMeTimer = new System.Timers.Timer(2500); 
    HideMeTimer.Elapsed += 
     delegate(object sender2, System.Timers.ElapsedEventArgs e2) 
     { 
      this.Invoke(new Action(somestuff)); 
     }; 
     HideMeTimer.Start(); 
     GC.Collect(); 
} 

Puede alguien por favor me muestran cómo el compilador se traducirá esto? O una buena explicación de por qué el temporizador sigue funcionando después del evento de carga.

(no es la regla cuando se ha ido la última referencia, a una variable, la GC en algún momento matar el objeto!)

+1

Evene si se pudo recopilar la referencia, que no puede, ** tiene la llamada para recopilar antes de que la variable salga del alcance. ** El recolector no va a recopilar algo que todavía está en el alcance. (El jitter está * permitido * para darse cuenta de que la última referencia a la variable está antes de la llamada para recopilar y permitir que se recopile, pero * en general * su técnica aquí no tiene sentido; no debe confiar en el jitter ¡diciéndole al recaudador que recoja cosas que todavía están en el alcance! –

+0

Es cierto - ¡gracias! – Niklas

+0

Esta pregunta se ha hecho muchas veces, vea la sección "relacionada" a la derecha. La respuesta corta es: los temporizadores son especiales; se mantienen vivos. De lo contrario, serían bastante inútiles, se presume que hizo un temporizador porque quiere que siga funcionando. –

Respuesta

10

Es necesario comprender lo que está haciendo System.Timers.Timer detrás de las escenas. Es un contenedor alrededor de la clase System.Threading.Timer. Cuando se inicia el temporizador crea una nueva instancia de System.Threading.Timer y le pasa un método de devolución de llamada en la instancia System.Timers.Timer. El sistema hace referencia a este delegado de devolución de llamada siempre que el temporizador permanezca habilitado.

También sabemos que los delegados mantienen una referencia a una instancia de la clase que contiene el método de destino. Esta es la razón por la que su instancia System.Timers.Timer no se recopila y no se detiene automáticamente. La eliminación del controlador de eventos Elapsed no resolverá este problema porque ese delegado solo contiene una referencia a la instancia TrayForm. Nuevamente, es el System.Threading.Timer el que mantiene una referencia al System.Timers.Timer. Toda la cadena de referencia permanece enraizada porque el sistema tiene que hacer referencia al System.Threading.Timer para que funcione.

Aquí es la cadena de referencia:

System => _TimerCallback => Callback => System.Threading.Timer => Callback => System.Timers.Timer 

Cuando realiza un seguimiento de esto con reflector o ILSpy se puede ver que el "sistema" en la cadena de referencia anterior entra en juego a través del método TimerBase.AddTimerNative que está marcado MethodImplOptions.InternalCall por lo que no podemos ver exactamente cómo la raíz de la referencia está debajo de este punto. Pero, está arraigado de todos modos.

Si desactiva el temporizador a través de Enabled = false o Stop entonces será disponer el System.Threading.Timer subyacente, que a su vez debería dejar de hacer referencia a la System.Timers.Timer.

+1

-1? Si alguien ve una declaración incorrecta en mi respuesta, hágamelo saber. Tengo que Revisé mi respuesta examinando el código BCL a través de ILSpy, pero es posible que haya olvidado algo. –

+1

No estoy seguro de quién votó negativamente, pero eres acertado. Eché un vistazo rápido al origen de 'AddTimerNative' y, de hecho, creo una raíz gc para el delegado' _TimerCallBack'. – dlev

+0

@dlev: Gracias por investigar sobre eso. Me preguntaba cómo 'AddTimerNative' iba a" rootear "esa devolución de llamada. Muy informativo. –

Cuestiones relacionadas