c# - ¿Cómo evitar fugas de identificadores al invocar en UI desde System.Threading.Timer?
.net winforms (4)
Parece que se llama a Invoke en un control de winforms en una devolución de llamada desde un System.Threading.Timer pierde las órdenes hasta que se elimina el temporizador. ¿Alguien tiene una idea de cómo solucionar esto? Necesito sondear un valor por segundo y actualizar la UI en consecuencia.
Lo intenté en un proyecto de prueba para asegurarme de que esa era la causa de la fuga, que es simplemente la siguiente:
System.Threading.Timer timer;
public Form1()
{
InitializeComponent();
timer = new System.Threading.Timer(new System.Threading.TimerCallback(DoStuff), null, 0, 500);
}
void DoStuff(object o)
{
this.Invoke(new Action(() => this.Text = "hello world"));
}
Esto perderá 2 manijas / segundo si miras en el administrador de tareas de Windows.
¿Hay alguna razón por la que no puedas usar System.Windows.Forms.Timer aquí? Si el temporizador está vinculado a esa forma, ni siquiera necesitará invocarlo.
Está bien, le di un poco más de tiempo y parece que en realidad no está goteando manijas, es solo la naturaleza indeterminada del recolector de basura. Subí hasta 10 ms por tick y trepaba muy rápido y 30 segundos después volvía a caer.
Para confirmar la teoría que llamé manualmente GC.Collect () en cada devolución de llamada (no haga esto en proyectos reales, esto fue solo para probar, hay numerosos artículos por ahí acerca de por qué es una mala idea) y el conteo de identificadores se mantuvo estable.
Interesante: esta no es una respuesta, pero basado en el comentario de Andrei, pensé que esto no daría problemas de la misma forma, pero sí filtra los identificadores a la misma velocidad que el OP.
System.Threading.Timer timer;
public Form2()
{
InitializeComponent();
}
private void UpdateFormTextCallback()
{
this.Text = "Hello World!";
}
private Action UpdateFormText;
private void DoStuff(object value)
{
this.Invoke(UpdateFormText);
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
timer = new System.Threading.Timer(new TimerCallback(DoStuff), null, 0, 500);
UpdateFormText = new Action(UpdateFormTextCallback);
}
Invoke actúa como un par BeginInvoke / EndInvoke porque publica el mensaje en el subproceso de interfaz de usuario, crea un identificador y espera en ese identificador para determinar cuándo se completa el método invocado. Es este mango el que está "goteando". Puede ver que estos son eventos sin nombre que usan Process Explorer para monitorear los identificadores mientras se ejecuta la aplicación.
Si IASyncResult era IDisposable, la eliminación del objeto se encargaría de limpiar el mango. Como no es así, los identificadores se limpian cuando el recolector de elementos no utilizados se ejecuta y llama al finalizador del objeto IASyncResult. Puede ver esto agregando un GC.Collect () después de cada 20 llamadas a DoStuff: el conteo de manejadores cae cada 20 segundos. Por supuesto, "resolver" el problema agregando llamadas a GC.Collect () es la forma incorrecta de abordar el problema; deja que el recolector de basura haga su propio trabajo.
Si no necesita que la llamada Invoke sea sincrónica, use BeginInvoke en lugar de Invoke y no llame a EndInvoke; el resultado final hará lo mismo, pero no se crearán ni se "filtrarán" identificadores.