Invocar (delegar)

93

¿Alguien puede explicar esta declaración escrita en este enlace?

Invoke(Delegate):

Ejecuta el delegado especificado en el subproceso que posee el identificador de ventana subyacente del control.

¿Alguien puede explicar qué significa esto (especialmente el en negrita)? No puedo entenderlo claramente

usuario1903439
fuente
4
La respuesta a esta pregunta está vinculada a la propiedad Control.InvokeRequired; consulte msdn.microsoft.com/en-us/library/…
dash

Respuestas:

131

La respuesta a esta pregunta radica en cómo funcionan los controles de C #

Los controles en Windows Forms están vinculados a un subproceso específico y no son seguros para subprocesos. Por lo tanto, si está llamando al método de un control desde un subproceso diferente, debe utilizar uno de los métodos de invocación del control para calcular la llamada al subproceso adecuado. Esta propiedad se puede usar para determinar si debe llamar a un método de invocación, lo que puede ser útil si no sabe qué subproceso posee un control.

Desde Control.InvokeRequired

Efectivamente, lo que hace Invoke es garantizar que el código al que está llamando se produzca en el subproceso en el que el control "sigue vivo" y previene de manera eficaz las excepciones entre subprocesos.

Desde una perspectiva histórica, en .Net 1.1, esto estaba realmente permitido. Lo que significaba es que podía intentar ejecutar código en el hilo "GUI" desde cualquier hilo en segundo plano y esto funcionaría principalmente. A veces, solo haría que su aplicación se cerrara porque estaba interrumpiendo efectivamente el hilo de la GUI mientras hacía otra cosa. Esta es la excepción de subprocesos cruzados : imagine intentar actualizar un TextBox mientras la GUI está pintando algo más.

  • ¿Qué acción tiene prioridad?
  • ¿Es posible que ambos sucedan a la vez?
  • ¿Qué sucede con todos los demás comandos que la GUI necesita para ejecutarse?

Efectivamente, estás interrumpiendo una cola, lo que puede tener muchas consecuencias imprevistas. Invoke es efectivamente la forma "educada" de obtener lo que desea hacer en esa cola, y esta regla se aplicó desde .Net 2.0 en adelante mediante una InvalidOperationException lanzada .

Para comprender lo que está sucediendo realmente detrás de escena y lo que se entiende por "hilo de interfaz gráfica de usuario", es útil comprender qué es una bomba de mensajes o un bucle de mensajes.

En realidad, esto ya está respondido en la pregunta " ¿Qué es una bomba de mensajes? " Y se recomienda leerlo para comprender el mecanismo real al que se está vinculando al interactuar con los controles.

Otras lecturas que pueden resultarle útiles incluyen:

¿Qué pasa con Begin Invoke?

Una de las reglas fundamentales de la programación de la GUI de Windows es que solo el hilo que creó un control puede acceder y / o modificar su contenido (excepto por unas pocas excepciones documentadas). Intente hacerlo desde cualquier otro hilo y obtendrá un comportamiento impredecible que va desde punto muerto, excepciones y una interfaz de usuario medio actualizada. La forma correcta de actualizar un control desde otro hilo es publicar un mensaje apropiado en la cola de mensajes de la aplicación. Cuando la bomba de mensajes llegue a ejecutar ese mensaje, el control se actualizará, en el mismo hilo que lo creó (recuerde, la bomba de mensajes se ejecuta en el hilo principal).

y, para una descripción general más pesada de código con una muestra representativa:

Operaciones de subprocesos cruzados no válidas

// the canonical form (C# consumer)

public delegate void ControlStringConsumer(Control control, string text);  // defines a delegate type

public void SetText(Control control, string text) {
    if (control.InvokeRequired) {
        control.Invoke(new ControlStringConsumer(SetText), new object[]{control, text});  // invoking itself
    } else {
        control.Text=text;      // the "functional part", executing only on the main thread
    }
}

Una vez que haya comprendido InvokeRequired, es posible que desee considerar el uso de un método de extensión para finalizar estas llamadas. Esto se trata hábilmente en la pregunta de desbordamiento de pila . Se requiere limpieza de código lleno de invocación .

También hay un escrito adicional de lo que sucedió históricamente que puede ser de interés.

guión
fuente
68

Un control o un objeto de ventana en Windows Forms es solo un envoltorio alrededor de una ventana de Win32 identificada por un identificador (a veces llamado HWND). La mayoría de las cosas que hace con el control eventualmente resultarán en una llamada a la API de Win32 que usa este identificador. El identificador es propiedad del hilo que lo creó (normalmente el hilo principal) y no debe ser manipulado por otro hilo. Si por alguna razón necesita hacer algo con el control de otro hilo, puede usar Invokepara pedirle al hilo principal que lo haga en su nombre.

Por ejemplo, si desea cambiar el texto de una etiqueta de un hilo de trabajo, puede hacer algo como esto:

theLabel.Invoke(new Action(() => theLabel.Text = "hello world from worker thread!"));
Thomas Levesque
fuente
¿Puedes explicar por qué alguien haría algo como esto? this.Invoke(() => this.Enabled = true);Todo lo que se thisrefiere seguramente está en el hilo actual, ¿verdad?
Kyle Delaney
1
@KyleDelaney, un objeto no está "en" un hilo, y el hilo actual no es necesariamente el hilo que creó el objeto.
Thomas Levesque
24

Si desea modificar un control debe hacerlo en el hilo en el que se creó el control. Este Invokemétodo le permite ejecutar métodos en el hilo asociado (el hilo que posee el identificador de ventana subyacente del control).

En el ejemplo siguiente thread1 lanza una excepción porque SetText1 está intentando modificar textBox1.Text de otro hilo. Pero en thread2, Action en SetText2 se ejecuta en el hilo en el que se creó TextBox

private void btn_Click(object sender, EvenetArgs e)
{
    var thread1 = new Thread(SetText1);
    var thread2 = new Thread(SetText2);
    thread1.Start();
    thread2.Start();
}

private void SetText1() 
{
    textBox1.Text = "Test";
}

private void SetText2() 
{
    textBox1.Invoke(new Action(() => textBox1.Text = "Test"));
}
Mehmet Ataş
fuente
Realmente me gusta este enfoque, oculta la naturaleza de los delegados, pero de todos modos es un buen atajo.
shytikov
7
Invoke((MethodInvoker)delegate{ textBox1.Text = "Test"; });
Mejor Desarrollador
fuente
El uso de System.Action que la gente sugiere en otras respuestas solo funciona en el marco 3.5+, para versiones anteriores, esto funciona perfectamente
Suicide Platypus
2

En términos prácticos, significa que se garantiza que el delegado se invocará en el hilo principal. Esto es importante porque en el caso de los controles de Windows, si no actualiza sus propiedades en el hilo principal, entonces no ve el cambio o el control genera una excepción.

El patrón es:

void OnEvent(object sender, EventArgs e)
{
   if (this.InvokeRequired)
   {
       this.Invoke(() => this.OnEvent(sender, e);
       return;
   }

   // do stuff (now you know you are on the main thread)
}
satnhak
fuente
2

this.Invoke(delegate)asegúrese de que está llamando al delegado del argumento en this.Invoke()el hilo principal / hilo creado.

Puedo decir que una regla de pulgar no accede a los controles de su formulario, excepto desde el hilo principal.

Puede que las siguientes líneas tengan sentido para usar Invoke ()

    private void SetText(string text)
    {
        // InvokeRequired required compares the thread ID of the
        // calling thread to the thread ID of the creating thread.
        // If these threads are different, it returns true.
        if (this.textBox1.InvokeRequired)
        {   
            SetTextCallback d = new SetTextCallback(SetText);
            this.Invoke(d, new object[] { text });
        }
        else
        {
            this.textBox1.Text = text;
        }
    }

Hay situaciones aunque crea un hilo de Threadpool (es decir, un hilo de trabajo) que se ejecutará en el hilo principal. No creará un nuevo hilo porque el hilo principal está disponible para procesar más instrucciones. Así que primero investigue si el hilo actual en ejecución es el hilo principal usando this.InvokeRequiredif devuelve verdadero el código actual se está ejecutando en el hilo de trabajo así que llame a this.Invoke (d, new object [] {text});

de lo contrario, actualice directamente el control de la interfaz de usuario (aquí se le garantiza que está ejecutando el código en el hilo principal).

Srikanth
fuente
1

Significa que el delegado se ejecutará en el subproceso de la IU, incluso si llama a ese método desde un trabajador en segundo plano o un subproceso de grupo de subprocesos. Los elementos de la interfaz de usuario tienen afinidad por los hilos ; solo les gusta hablar directamente con un hilo: el hilo de la interfaz de usuario. El subproceso de la interfaz de usuario se define como el subproceso que creó la instancia de control y, por lo tanto, está asociado con el identificador de la ventana. Pero todo eso es un detalle de implementación.

El punto clave es: llamaría a este método desde un hilo de trabajo para poder acceder a la interfaz de usuario (para cambiar el valor en una etiqueta, etc.), ya que no puede hacerlo desde ningún otro hilo que no sea el hilo de la interfaz de usuario.

Marc Gravell
fuente
0

Los delegados son esencialmente en línea Actiono Func<T>. Puede declarar un delegado fuera del alcance de un método que está ejecutando o usando una lambdaexpresión ( =>); debido a que ejecuta el delegado dentro de un método, lo ejecuta en el hilo que se está ejecutando para la ventana / aplicación actual, que es el bit en negrita.

Ejemplo de lambda

int AddFiveToNumber(int number)
{
  var d = (int i => i + 5);
  d.Invoke(number);
}
LukeHennerley
fuente
0

Significa que el delegado que pasa se ejecuta en el hilo que creó el objeto Control (que es el hilo de la IU).

Debe llamar a este método cuando su aplicación es multiproceso y desea realizar alguna operación de IU desde un subproceso que no sea el subproceso de la IU, porque si solo intenta llamar a un método en un Control desde un subproceso diferente, obtendrá un System.InvalidOperationException.

user1610015
fuente