¿Cómo evitar manijas con fugas cuando se invoca en la interfaz de usuario desde System.Threading.Timer?

StackOverflow https://stackoverflow.com/questions/1603123

Pregunta

Parece que se invoca a Invoke en un control winforms en una devolución de llamada desde un System.Threading.Timer filtra los controladores hasta que se elimine el temporizador. ¿Alguien tiene una idea de cómo solucionar esto? Necesito sondear un valor cada segundo y actualizar la IU en consecuencia.

Lo intenté en un proyecto de prueba para asegurarme de que esa fuera 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 manejadores / segundo si observa en el administrador de tareas de Windows.

¿Fue útil?

Solución

Invoke actúa como un par BeginInvoke / EndInvoke en el sentido de que publica el mensaje en el hilo de la interfaz de usuario, crea un identificador y espera en ese identificador para determinar cuándo se completa el método invocado. Es esta manija la que está goteando. Puede ver que estos son eventos sin nombre usando Process Explorer para monitorear los controladores mientras la aplicación se está ejecutando.

Si IASyncResult era IDisponible, la eliminación del objeto se haría cargo de limpiar el mango. Como no lo es, los manejadores se limpian cuando el recolector de basura 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 recuento del identificador disminuye cada 20 segundos. Por supuesto, "resolviendo" el problema al agregar llamadas a GC.Collect () es la forma incorrecta de abordar el problema; Deje 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 manejarán filtraciones. "

Otros consejos

¿Hay alguna razón por la que no pueda usar un System.Windows.Forms.Timer aquí? Si el temporizador está vinculado a ese formulario, ni siquiera tendrá que invocar.

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. Lo alcancé hasta 10 ms por tic y subirá muy rápido y 30 segundos más tarde volverá a bajar.

PARA confirmar la teoría, 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 sobre por qué es una mala idea) y el recuento de identificadores era estable.

Interesante: esta no es una respuesta, pero basándome en el comentario de Andrei, hubiera pensado que esto no filtraría los manejadores de la misma manera, pero sí filtra los manejadores al mismo ritmo que el OP mencionado.

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);
    }
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top