Rendimiento del controlador de eventos

Tengo un problema de rendimiento. Creo 100 botones nuevos y quiero asignar un Administrador de eventos de clic. Ejecuto este código unas 100 veces:

Buttons[i].Button.Click += new System.EventHandler(Button_Click);

Tarda unos 2 segundos en completarse. Tengo muchas otras asignaciones de eventos en la misma función, pero todas tardan solo unos milisegundos en ejecutarse. Así que he transformado mi código en

Buttons[i].Button.MouseUp += new System.Windows.Forms.MouseEventHandler(Button_Click);

Ahora el código es rápido (algunos milisegundos, como los demás). Obviamente, modifiqué los parámetros de la función "Button_click" para adaptarse a los nuevos requisitos del evento, pero no se realizaron otros cambios.

Me pregunto por qué podría suceder esto. ¿EventHandler es tan lento? ¿O estoy haciendo algo mal? ¿O existe una mejor práctica?

Estoy usando VC2010 con C #, usando .NET 4 en una aplicación de Windows Form.

EDIT:

Ahora he "minimizado" mi código y lo puse allí:

            Stopwatch stopWatch = new Stopwatch();
            stopWatch.Start();
            Button b;
            for(n=0;n<100;n++)
            {
                b = new Button();
                b.Location = new System.Drawing.Point(100, 0);
                b.Name = "btnGrid";
                b.Size = new System.Drawing.Size(50, 50);
                b.Text = b.Name;
                b.UseVisualStyleBackColor = true;
                b.Visible = false;
                b.Text = "..";
                b.Click += new EventHandler(this.Button_Click);
                //b.MouseUp += new System.Windows.Forms.MouseEventHandler(this.Button_ClickUP);
            }
            stopWatch.Stop();

            TimeSpan ts = stopWatch.Elapsed;
            string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds / 10);
            Log(elapsedTime, Color.Purple);

Button_Click y Button_Click son:

    private void Button_Click(object sender, EventArgs e)
    {            
    }

    private void Button_ClickUP(object sender, MouseEventArgs e)
    {
    }

Pongo este código en un botón y la función "Registro" muestra el resultado en una nota. Cuando habilito "Click" el resultado es 01.05 seg, pero cuando habilito "MouseUp" el resultado es 00.00.

Diferencia -> ¡UN SEGUNDO!

¿¡por qué!?

== EDITAR ==

Yo uso .NET Framework 4. VS2010. Gana XP. Encontré esto: si utilizo .NET 3.5 o menor, la velocidad cambia: 0.5 seg. La mitad. Si compilo en modo de depuración o lanzamiento, no cambia.

Si utilizo el ejecutable sin el depurador es increíblemente rápido.

Entonces cambio mi pregunta: ¿.NET 4 es más lento que .NET 3? ¿Por qué el modo de lanzamiento funciona de manera diferente en comparación con la versión independiente?

Muchas gracias.

preguntado el 10 de mayo de 11 a las 14:05

Me cuesta imaginar una situación en la que 100 botones en un solo formulario podrían ser un buen diseño ... -

@CodyGray: Buscaminas :) -

@JonB: Demasiados; parece que siempre me equivoco. -

¿Cómo ha determinado que esta línea es la que le toma dos segundos? Me parece muy poco probable que, en más de 100 iteraciones, agregar un controlador Click frente a agregar un controlador MouseUp pueda tener una diferencia de rendimiento tan dramática. ¿Puedes armar una aplicación de consola mínima que reproduzca el comportamiento? -

Háganos saber cómo solucionará esto eventualmente, tengo curiosidad y si es como se describe, será importante saberlo. -

4 Respuestas

El código ".Click + = ..." se transforma en ".add_Click (...)". El método "add_Click" puede tener algunas comprobaciones lógicas.

Puede acelerar un poco sin la recreación del delegado:

EventHandler clickHandler = this.Button_Click;
foreach(Button btn in GetButtons()) {
   btn.Click += clicHandler;
}

EDIT:

¿Estás seguro de que el cuello de botella es la unión de los manipuladores? Probé el bucle for (100 bucles) adjuntando el controlador de eventos al evento Click y obtengo estos resultados:

/* only creation the button and attaching the handler */
button1_Click - A: 0 ms
button1_Click - B: 0 ms
button1_Click - A: 1 ms
button1_Click - B: 0 ms
button1_Click - A: 0 ms
button1_Click - B: 0 ms

/* creation the button, attaching the handler and add to the panel */
button2_Click - A: 223 ms
button2_Click - B: 202 ms
button2_Click - A: 208 ms
button2_Click - B: 201 ms
button2_Click - A: 204 ms
button2_Click - B: 230 ms

El código fuente:

    void button_Click(object sender, EventArgs e) {
        // do nothing
    }

    private void button1_Click(object sender, EventArgs e) {
        const int MAX_BUTTONS = 100;
        var stopWatch = new System.Diagnostics.Stopwatch();
        stopWatch.Start();
        for (int i = 0; i < MAX_BUTTONS; i++) {
            var button = new Button();
            button.Click += new EventHandler(button_Click);
        }
        stopWatch.Stop();
        System.Diagnostics.Debug.WriteLine(string.Format("button1_Click - A: {0} ms", stopWatch.ElapsedMilliseconds));

        stopWatch.Reset();
        stopWatch.Start();
        EventHandler clickHandler = this.button_Click;
        for (int i = 0; i < MAX_BUTTONS; i++) {
            var button = new Button();
            button.Click += clickHandler;
        }
        stopWatch.Stop();
        System.Diagnostics.Debug.WriteLine(string.Format("button1_Click - B: {0} ms", stopWatch.ElapsedMilliseconds));
    }

    private void button2_Click(object sender, EventArgs e) {
        const int MAX_BUTTONS = 100;

        var stopWatch = new System.Diagnostics.Stopwatch();

        this.panel1.Controls.Clear();
        stopWatch.Start();
        for (int i = 0; i < MAX_BUTTONS; i++) {
            var button = new Button();
            button.Click += new EventHandler(button_Click);
            this.panel1.Controls.Add(button);
        }
        stopWatch.Stop();
        System.Diagnostics.Debug.WriteLine(string.Format("button2_Click - A: {0} ms", stopWatch.ElapsedMilliseconds));

        stopWatch.Reset();

        this.panel1.Controls.Clear();
        stopWatch.Start();
        EventHandler clickHandler = this.button_Click;
        for (int i = 0; i < MAX_BUTTONS; i++) {
            var button = new Button();
            button.Click += clickHandler;
            this.panel1.Controls.Add(button);
        }
        stopWatch.Stop();
        System.Diagnostics.Debug.WriteLine(string.Format("button2_Click - B: {0} ms", stopWatch.ElapsedMilliseconds));
    }

EDIT 2: Intenté comparar el tiempo dedicado a adjuntar el controlador Click frente al controlador MouseUp. No parece que el evento MouseUp adjunto sea más rápido que el evento Click.

Creo que el problema estará en otro lado. ¿No recolecta GC durante su ciclo? ¿O no haces otra cosa allí?

Resultados:

button1_Click - Click_A: 6 ms
button1_Click - Click_B: 6 ms
button1_Click - MouseUp_A: 15 ms
button1_Click - MousUp_B: 7 ms

button1_Click - Click_A: 16 ms
button1_Click - Click_B: 7 ms
button1_Click - MouseUp_A: 16 ms
button1_Click - MousUp_B: 10 ms

button1_Click - Click_A: 14 ms
button1_Click - Click_B: 19 ms
button1_Click - MouseUp_A: 27 ms
button1_Click - MousUp_B: 5 ms

button1_Click - Click_A: 17 ms
button1_Click - Click_B: 17 ms
button1_Click - MouseUp_A: 24 ms
button1_Click - MousUp_B: 8 ms

button1_Click - Click_A: 6 ms
button1_Click - Click_B: 5 ms
button1_Click - MouseUp_A: 14 ms
button1_Click - MousUp_B: 7 ms

button1_Click - Click_A: 14 ms
button1_Click - Click_B: 9 ms
button1_Click - MouseUp_A: 15 ms
button1_Click - MousUp_B: 7 ms

Código:

    private void button1_Click(object sender, EventArgs e) {
        const int MAX_BUTTONS = 1000;
        var stopWatch = new System.Diagnostics.Stopwatch();

        stopWatch.Start();
        for (int i = 0; i < MAX_BUTTONS; i++) {
            var button = new Button();
            button.Click += new EventHandler(button_Click);
        }
        stopWatch.Stop();
        System.Diagnostics.Debug.WriteLine(string.Format("button1_Click - Click_A: {0} ms", stopWatch.ElapsedMilliseconds));

        stopWatch.Reset();
        stopWatch.Start();
        EventHandler clickHandler = this.button_Click;
        for (int i = 0; i < MAX_BUTTONS; i++) {
            var button = new Button();
            button.Click += clickHandler;
        }
        stopWatch.Stop();
        System.Diagnostics.Debug.WriteLine(string.Format("button1_Click - Click_B: {0} ms", stopWatch.ElapsedMilliseconds));

        stopWatch.Start();
        for (int i = 0; i < MAX_BUTTONS; i++) {
            var button = new Button();
            button.MouseUp += new MouseEventHandler(button_MouseUp);
        }
        stopWatch.Stop();
        System.Diagnostics.Debug.WriteLine(string.Format("button1_Click - MouseUp_A: {0} ms", stopWatch.ElapsedMilliseconds));

        stopWatch.Reset();
        stopWatch.Start();
        MouseEventHandler mouseUpHandler = this.button_MouseUp;
        for (int i = 0; i < MAX_BUTTONS; i++) {
            var button = new Button();
            button.MouseUp += mouseUpHandler;
        }
        stopWatch.Stop();
        System.Diagnostics.Debug.WriteLine(string.Format("button1_Click - MousUp_B: {0} ms", stopWatch.ElapsedMilliseconds));
    }

EDIT: El cuerpo de add_Click método (= Click += ...) es áspero:

public void add_Click(EventHandler value) {
   this.Events.AddHandler(ClickEventIdentifier, value);
}

Los eventos MouseUp se verán similares. Al menos ambos eventos usando Events propiedad para la celebración de listas de delegados para eventos.

Pero si probé varias cosas, no puedo obtener los problemas con los eventos como escribiste :(. ¿Puedes reproducir el mismo comportamiento en otras computadoras?

contestado el 13 de mayo de 11 a las 11:05

@TcKs: Esa también fue mi primera suposición, pero no hay verificación lógica. Además, tu sugerencia no acelera nada, porque btn.Click += this.Button_Click también es válido. Si acelerara las cosas, no habría una explicación lógica de por qué las asignaciones a MouseUp son más rápidos. - Daniel Hilgarth

@Daniel Hilgart: Probé el bucle for con 1000 botones y el tiempo invertido es realmente bajo (4ms - 15ms). ¿Estás seguro de que el problema está en adjuntar eventos? - TcKs

@TcKs: ¡No, no estoy seguro! Pero nunca dije eso de todos modos. Mi respuesta intenta proporcionar una explicación de por qué agregar un controlador de eventos a Click es más lento que agregar uno a MouseUp bajo el supuesto de que este es realmente el caso. - Daniel Hilgarth

@Daniel Hilgarth: Mira la segunda parte de edición. No parece que el evento adjunto MouseUp sea más rápido que el evento Click. - TcKs

@TcKs: si intentó implementar la comparación que sugerí en mi respuesta, debe cambiarla, para que el segundo for loop usa los mismos botones que el primero. Pero como ya dije: es muy posible que las observaciones del OP no estén libres de efectos secundarios, es decir, son Mal. Sin embargo, basé mi respuesta en la suposición de que son correctas. - Daniel Hilgarth

System.EventHandler es un tipo de delegado y por lo tanto no do cualquier cosa. Ésta no puede ser la causa de la diferencia en el rendimiento.
Añadiendo un nuevo Click manejador y un nuevo MouseUp El controlador de eventos es el mismo internamente. Ambos llaman Events.AddHandler.
En mi opinión, la única diferencia puede ser que o Click ya tiene otros controladores de eventos adjuntos y MouseUp no tiene o al revés.
Para comprobar si mi suposición es correcta, puede copiar y pegar los dos fragmentos de código y ejecutarlos dos veces y medir la duración por primera y segunda vez.

Si ambos corren por Click son lentos y la segunda carrera para MouseUp es lento, el problema es que ya existen Click controladores y agregar un controlador cuando ya existe uno es más lento que agregar uno cuando no existe ninguno.

Si la primera carrera para Click es lento y el segundo es rápido y ambos se ejecutan para MouseUp son rápidos, el problema es que no existen Click controladores y agregar un controlador cuando ya existe uno es Más rápida que agregar uno cuando no existe ninguno.

Mi respuesta asume que las observaciones del OP están libres de efectos secundarios. En realidad no lo hice prueba si sus resultados son reproducibles o plausibles. Mi respuesta solo quiere mostrar que realmente no hay nada especial en el Click evento o System.EventHandler.

contestado el 10 de mayo de 11 a las 18:05

Háganos saber cómo finalmente solucionará este problema. Tengo curiosidad y, si es como se describe, será importante saberlo. - usuario159335

@JonB: ¿Supongo que su comentario estaba destinado a ser publicado en la pregunta? - Daniel Hilgarth

Dios, no estoy con eso hoy, ese es el segundo. Hora del café. - usuario159335

¡Tengo curiosidad por hacerlo! Puedo decir que no hay otros controladores de eventos. Probé también agregando un segundo controlador de eventos a MouseUp: sin acelerar. - Redax

@Redax: compruebe las medidas de rendimiento realizadas por otros. Normalmente, no debería haber una diferencia, es decir, debe intentar extraer la diferencia entre su código y el de ellos para aprender, de dónde viene. - Daniel Hilgarth

Intenté esto: -

  public partial class Form1 : Form
  {
    List<Button> buttonList = new List<Button>();
    public Form1()
    {
      for (int n = 0; n < 100; n++)
      {
        Button tempButt = new Button();
        tempButt.Top = n*5;
        tempButt.Left = n * 5;
        this.Controls.Add(tempButt);
        buttonList.Add(tempButt);
      }
      var stopwatch = new Stopwatch();
      stopwatch.Start();
      foreach (Button butt in buttonList)
      {
        butt.Click += new System.EventHandler(button1_Click);
      }
      stopwatch.Stop();
      Console.WriteLine(stopwatch.ElapsedMilliseconds);
    }

    private void button1_Click(object sender, EventArgs e)
    {
      Console.WriteLine("Cheese");
    }
  }

Todos los botones aparecen y los eventos parecen dispararse correctamente, la asignación del controlador de eventos es demasiado rápida para medir de manera sensata. Mucho menos de un segundo en mi máquina.

¿Quizás el problema esté en otra parte?

contestado el 10 de mayo de 11 a las 19:05

¡Tal vez! Sigo buscando. ¿Alguna pista? En mi máquina, este código es muy lento, pero es rápido como dices con MouseUP. - Redax

Intente apagar IntelliTrace. Tenía exactamente los mismos síntomas. Increíblemente rápido cuando no se ejecuta en Visual Studio, pero siempre se agregan 30 ms por btn. Haga clic en + = hice si se ejecuta en Visual Studio.

Entonces, probablemente IntelliTrace que se engancha en los eventos Click de alguna manera para una depuración detallada.

Respondido el 12 de junio de 15 a las 15:06

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.