Automatización del patrón de código InvokeRequired

Resuelto Tom Corelis asked hace 14 años • 9 respuestas

Me he vuelto dolorosamente consciente de la frecuencia con la que es necesario escribir el siguiente patrón de código en 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. ¿A alguien se le ha ocurrido 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 realice esta verificación sin tener que realizar todo este trabajo adicional, como un object1.InvokeIfNecessary.visible = trueatajo de tipo.

Las respuestas anteriores han discutido la impracticabilidad de simplemente llamar a Invoke() cada vez, e incluso entonces la sintaxis de Invoke() es ineficiente y aún incómoda de manejar.

Entonces, ¿alguien ha descubierto algún atajo?

Tom Corelis avatar Mar 03 '10 06:03 Tom Corelis
Aceptado

El enfoque de Lee se puede simplificar 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 .

Si debe devolver un valor, puede utilizar esta implementación:

private static T InvokeIfRequiredReturn<T>(this Control control, Func<T> function)
{
    if (control.InvokeRequired) {
        return (T)control.Invoke(function);
    } else {
        return function();
    }
}

ACTUALIZAR :

Según varios otros carteles Control, se puede generalizar como ISynchronizeInvoke:

public static void InvokeIfRequired(this ISynchronizeInvoke obj,
                                         MethodInvoker action)
{
    if (obj.InvokeRequired) {
        obj.Invoke(action, null);
    } else {
        action();
    }
}

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

De acuerdo con la documentación del método ISynchronizeInvoke.Invoke(Delegate, Object[]) podemos pasar nullsi no se necesitan argumentos.


ACTUALIZACIÓN 2

Ediciones sugeridas por Mike de Klerk (consulte el comentario en el primer fragmento de código para ver 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 los comentarios de ToolmakerSteve y nawfal a continuación si tiene dudas sobre esta sugerencia.

Olivier Jacot-Descombes avatar Aug 29 '2012 13:08 Olivier Jacot-Descombes

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 señala Simpzon en los comentarios, también puede cambiar la firma a:

public static void InvokeIfRequired<T>(this T c, Action<T> action) 
    where T : Control
Lee avatar Mar 02 '2010 23:03 Lee

Este es el formulario que he estado usando en todo mi código.

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

Me he basado en la entrada del blog aquí . Este enfoque no me ha fallado, por lo que no veo ninguna razón para complicar mi código con una verificación de la InvokeRequiredpropiedad.

Espero que esto ayude.

Matt Davis avatar Mar 03 '2010 00:03 Matt Davis

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 avatar Feb 15 '2011 13:02 Aaron Gage

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 avatar Apr 07 '2015 17:04 gxtaillon