Automatizar el patrón de código InvokeRequired

179

Me he dado cuenta de cuán a menudo es necesario escribir el siguiente patrón de código en el código GUI controlado por eventos, donde

private void DoGUISwitch() {
    // cruisin for a bruisin' through exception city
    object1.Visible = true;
    object2.Visible = false;
}

se convierte en:

private void DoGUISwitch() {
    if (object1.InvokeRequired) {
        object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); }));
    } else {
        object1.Visible = true;
        object2.Visible = false;
    }
}

Este es un patrón incómodo en C #, tanto para recordar como para escribir. ¿Alguien ha encontrado algún tipo de atajo o construcción que automatice esto hasta cierto punto? Sería genial si hubiera una manera de adjuntar una función a los objetos que realiza esta comprobación sin tener que pasar por todo este trabajo adicional, como un object1.InvokeIfNecessary.visible = trueacceso directo de tipo.

Las respuestas anteriores han discutido la impracticabilidad de llamar a Invoke () cada vez, e incluso entonces la sintaxis de Invoke () es ineficiente y aún difícil de manejar.

Entonces, ¿alguien ha descubierto algún atajo?

Tom Corelis
fuente
2
Me he preguntado lo mismo, pero en lo que respecta a Dispatcher.CheckAccess () de WPF.
Taylor Leese
Pensé en una sugerencia bastante loca inspirada en tu object1.InvokeIfNecessary.Visible = truelínea; mira mi respuesta actualizada y déjame saber lo que piensas.
Dan Tao
1
Agregue un fragmento para ayudar a implementar el método sugerido por Matt Davis: vea mi respuesta (tarde pero solo muestra cómo para lectores posteriores ;-))
Aaron Gage
3
No entiendo por qué Microsoft no hizo nada para simplificar eso en .NET. Crear delegados para cada cambio en el formulario desde el hilo es realmente molesto.
Kamil
@Kamil ¡No podría estar más de acuerdo! Este es un descuido, dada su ubicuidad. Dentro del marco, solo maneje el enhebrado si es necesario. Parece obvio
SteveCinq

Respuestas:

138

El enfoque de Lee puede simplificarse aún más

public static void InvokeIfRequired(this Control control, MethodInvoker action)
{
    // See Update 2 for edits Mike de Klerk suggests to insert here.

    if (control.InvokeRequired) {
        control.Invoke(action);
    } else {
        action();
    }
}

Y se puede llamar así

richEditControl1.InvokeIfRequired(() =>
{
    // Do anything you want with the control here
    richEditControl1.RtfText = value;
    RtfHelpers.AddMissingStyles(richEditControl1);
});

No es necesario pasar el control como parámetro al delegado. C # crea automáticamente un cierre .


ACTUALIZACIÓN :

Según varios otros carteles Controlse pueden generalizar como ISynchronizeInvoke:

public static void InvokeIfRequired(this ISynchronizeInvoke obj,
                                         MethodInvoker action)
{
    if (obj.InvokeRequired) {
        var args = new object[0];
        obj.Invoke(action, args);
    } else {
        action();
    }
}

DonBoitnott señaló que, a diferencia de Controlla ISynchronizeInvokeinterfaz, se requiere una matriz de objetos para el Invokemétodo como lista de parámetros para el action.


ACTUALIZACIÓN 2

Ediciones sugeridas por Mike de Klerk (ver comentario en el primer fragmento de código para el punto de inserción):

// When the form, thus the control, isn't visible yet, InvokeRequired  returns false,
// resulting still in a cross-thread exception.
while (!control.Visible)
{
    System.Threading.Thread.Sleep(50);
}

Consulte el comentario de ToolmakerSteve a continuación para conocer las inquietudes sobre esta sugerencia.

Olivier Jacot-Descombes
fuente
2
¿No sería mejor tener en ISynchronizeInvokelugar de Control? (Felicitaciones a Jon Skeet stackoverflow.com/questions/711408/… )
Odys
@odyodyodys: Buen punto. No sabía acerca ISynchronizeInvoke. Pero el único tipo que se deriva de él (según Reflector) es Control, por lo que la ventaja es limitada.
Olivier Jacot-Descombes
3
@ Mike-de-clerk, me preocupa su sugerencia de agregar while (!control.Visible) ..sleep... Para mí, eso tiene un mal olor de código, ya que es un retraso potencialmente ilimitado (tal vez incluso un bucle infinito en algunos casos), en un código que puede tener llamadas que no esperan ese retraso (o incluso un punto muerto). En mi humilde opinión, cualquier uso de Sleepdebe ser responsabilidad de cada persona que llama, O debe estar en un contenedor separado que esté claramente marcado en cuanto a sus consecuencias. En mi humilde opinión, generalmente sería mejor "fallar con fuerza" (excepción, atrapar durante la prueba) o "no hacer nada" si el control no está listo. Comentarios?
ToolmakerSteve
1
@ OlivierJacot-Descombes, sería genial, por favor, explique cómo funciona thread.invokerequired detrás.
Sudhir.net
1
InvokeRequiredindica si el hilo de llamada es diferente del hilo que creó el control. Invokepasa la acción del hilo de llamada al hilo del control donde se ejecuta. Esto garantiza que, por ejemplo, un controlador de eventos de clic nunca se interrumpa.
Olivier Jacot-Descombes
133

Podrías escribir un método de extensión:

public static void InvokeIfRequired(this Control c, Action<Control> action)
{
    if(c.InvokeRequired)
    {
        c.Invoke(new Action(() => action(c)));
    }
    else
    {
        action(c);
    }
}

Y úsalo así:

object1.InvokeIfRequired(c => { c.Visible = true; });

EDITAR: Como Simpzon señala en los comentarios, también puede cambiar la firma a:

public static void InvokeIfRequired<T>(this T c, Action<T> action) 
    where T : Control
Sotavento
fuente
Tal vez soy demasiado tonto, pero este código no se compilará. Así que lo arreglé tal como lo construí yo (VS2008).
Oliver
55
Solo para completar: en WPF hay un mecanismo de despacho diferente, pero funciona de manera bastante análoga. Podría usar este método de extensión allí: public static void InvokeIfRequired <T> (this T aTarget, Action <T> aActionToExecute) donde T: DispatcherObject {if (aTarget.CheckAccess ()) {aActionToExecute (aTarget); } else {aTarget.Dispatcher.Invoke (aActionToExecute); }}
Simon D.
1
Agregué una respuesta que simplifica ligeramente la solución de Lee.
Olivier Jacot-Descombes
Hola, como uso algo similar, puede haber un gran problema derivado de esta implementación genérica. Si el control está desechando / eliminado, obtendrá una excepción ObjectDisposedException.
Offler
1
@Offler: bueno, si se eliminan en un subproceso diferente, tiene un problema de sincronización, no es un problema en este método.
Lee
33

Aquí está el formulario que he estado usando en todo mi código.

private void DoGUISwitch()
{ 
    Invoke( ( MethodInvoker ) delegate {
        object1.Visible = true;
        object2.Visible = false;
    });
} 

He basado esto en la entrada del blog aquí . No me ha fallado este enfoque, por lo que no veo ninguna razón para complicar mi código con un control de la InvokeRequiredpropiedad.

Espero que esto ayude.

Matt Davis
fuente
+1: me topé con la misma entrada de blog que hiciste, y creo que este es el enfoque más limpio de cualquier propuesta
Tom Bushell,
3
Hay un pequeño impacto en el rendimiento que utiliza este enfoque, que podría acumularse cuando se llama varias veces. stackoverflow.com/a/747218/724944
surfen
44
Debe usar InvokeRequiredsi el código se puede ejecutar antes de que se muestre el control o tendrá una excepción fatal.
56
9

Cree un archivo ThreadSafeInvoke.snippet, y luego puede seleccionar las declaraciones de actualización, hacer clic derecho y seleccionar 'Rodear con ...' o Ctrl-K + S:

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippet Format="1.0.0" xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <Header>
    <Title>ThreadsafeInvoke</Title>
    <Shortcut></Shortcut>
    <Description>Wraps code in an anonymous method passed to Invoke for Thread safety.</Description>
    <SnippetTypes>
      <SnippetType>SurroundsWith</SnippetType>
    </SnippetTypes>
  </Header>
  <Snippet>
    <Code Language="CSharp">
      <![CDATA[
      Invoke( (MethodInvoker) delegate
      {
          $selected$
      });      
      ]]>
    </Code>
  </Snippet>
</CodeSnippet>
Aaron Gage
fuente
6

Aquí hay una versión mejorada / combinada de las respuestas de Lee, Oliver y Stephan.

public delegate void InvokeIfRequiredDelegate<T>(T obj)
    where T : ISynchronizeInvoke;

public static void InvokeIfRequired<T>(this T obj, InvokeIfRequiredDelegate<T> action)
    where T : ISynchronizeInvoke
{
    if (obj.InvokeRequired)
    {
        obj.Invoke(action, new object[] { obj });
    }
    else
    {
        action(obj);
    }
} 

La plantilla permite un código flexible y sin conversión que es mucho más legible, mientras que el delegado dedicado proporciona eficiencia.

progressBar1.InvokeIfRequired(o => 
{
    o.Style = ProgressBarStyle.Marquee;
    o.MarqueeAnimationSpeed = 40;
});
gxtaillon
fuente
4

Prefiero usar una sola instancia de un método Delegado en lugar de crear una nueva instancia cada vez. En mi caso, solía mostrar mensajes de progreso y (información / error) de un Backroundworker copiando y enviando datos grandes desde una instancia de SQL. Mientras tanto, después de aproximadamente 70000 llamadas de progreso y mensajes, mi formulario dejó de funcionar y mostraba nuevos mensajes. Esto no ocurrió cuando comencé a usar un solo delegado de instancia global.

delegate void ShowMessageCallback(string message);

private void Form1_Load(object sender, EventArgs e)
{
    ShowMessageCallback showMessageDelegate = new ShowMessageCallback(ShowMessage);
}

private void ShowMessage(string message)
{
    if (this.InvokeRequired)
        this.Invoke(showMessageDelegate, message);
    else
        labelMessage.Text = message;           
}

void Message_OnMessage(object sender, Utilities.Message.MessageEventArgs e)
{
    ShowMessage(e.Message);
}
Stephan Schmuck
fuente
3

Uso:

control.InvokeIfRequired(c => c.Visible = false);

return control.InvokeIfRequired(c => {
    c.Visible = value

    return c.Visible;
});

Código:

using System;
using System.ComponentModel;

namespace Extensions
{
    public static class SynchronizeInvokeExtensions
    {
        public static void InvokeIfRequired<T>(this T obj, Action<T> action)
            where T : ISynchronizeInvoke
        {
            if (obj.InvokeRequired)
            {
                obj.Invoke(action, new object[] { obj });
            }
            else
            {
                action(obj);
            }
        }

        public static TOut InvokeIfRequired<TIn, TOut>(this TIn obj, Func<TIn, TOut> func) 
            where TIn : ISynchronizeInvoke
        {
            return obj.InvokeRequired
                ? (TOut)obj.Invoke(func, new object[] { obj })
                : func(obj);
        }
    }
}
Konstantin S.
fuente
2

Me gusta hacerlo un poco diferente, me gusta llamarme a mí mismo si es necesario con una Acción,

    private void AddRowToListView(ScannerRow row, bool suspend)
    {
        if (IsFormClosing)
            return;

        if (this.InvokeRequired)
        {
            var A = new Action(() => AddRowToListView(row, suspend));
            this.Invoke(A);
            return;
        }
         //as of here the Code is thread-safe

este es un patrón útil, IsFormClosing es un campo que configuro en True cuando estoy cerrando mi formulario, ya que puede haber algunos subprocesos en segundo plano que todavía se están ejecutando ...

Walter Verhoeven
fuente
-3

Nunca deberías escribir código que se vea así:

private void DoGUISwitch() {
    if (object1.InvokeRequired) {
        object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); }));
    } else {
        object1.Visible = true;
        object2.Visible = false;
    }
}

Si tiene un código que se ve así, entonces su aplicación no es segura para subprocesos. Significa que tiene un código que ya está llamando a DoGUISwitch () desde un hilo diferente. Es demasiado tarde para verificar si está en un hilo diferente. InvokeRequire debe llamarse ANTES de hacer una llamada a DoGUISwitch. No debe acceder a ningún método o propiedad desde un hilo diferente.

Referencia: Control.InvokeRequired Propiedad donde puede leer lo siguiente:

Además de la propiedad InvokeRequired, hay cuatro métodos en un control que son seguros para la ejecución de subprocesos: Invoke, BeginInvoke, EndInvoke y CreateGraphics si el identificador para el control ya se ha creado.

En una arquitectura de CPU única no hay problema, pero en una arquitectura de CPU múltiple puede hacer que parte del subproceso de la interfaz de usuario se asigne al procesador donde se estaba ejecutando el código de llamada ... y si ese procesador es diferente de donde el subproceso de la interfaz de usuario se estaba ejecutando cuando finaliza el subproceso de llamada. Windows pensará que el subproceso de la interfaz de usuario ha finalizado y eliminará el proceso de la aplicación, es decir, su aplicación se cerrará sin errores.

Steve Wood
fuente
Hola, gracias por tu respuesta. Han pasado años desde que hice esta pregunta (y casi el mismo tiempo desde que trabajé con C #), pero me preguntaba si podría explicar un poco más. Los documentos que vinculó se refieren a un peligro específico de llamar a invoke()et al antes de que se le dé un control al control, pero en mi humilde opinión, no describe lo que ha descrito. El punto de todas estas invoke()tonterías es actualizar la interfaz de usuario de una manera segura para subprocesos, y creo que poner más instrucciones en un contexto de bloqueo conduciría a la tartamudez. (Ugh ... me alegro de haber dejado de usar M $ tech. ¡Tan complicado!)
Tom Corelis
También quiero señalar que, a pesar del uso frecuente del código original (hace mucho tiempo), no observé el problema que describió en mi escritorio con doble CPU
Tom Corelis,
3
Dudo que esta respuesta sea precisa ya que MSDN muestra muchos ejemplos al igual que el OP dio.
inalámbrico público