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


Me he dado cuenta dolorosamente de la frecuencia con la que uno necesita escribir el siguiente patrón de código en código GUI impulsado 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 ideado 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 hace esta comprobación sin tener que pasar por todo este trabajo adicional, como un tipo object1.InvokeIfNecessary.visible = true atajo.

Respuestas anteriores han discutido la impracticalidad de solo llamar a Invoke() cada vez, e incluso entonces la sintaxis Invoke() es ineficiente y sigue siendo difícil de tratar.

Entonces, ¿alguien ha descubierto algún atajo?

Author: Community, 2010-03-03

9 answers

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 :

De acuerdo con varios otros carteles Control se puede 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 Control la interfaz ISynchronizeInvoke requiere una matriz de objetos para el método Invoke como parámetro list for the action.


ACTUALIZACIÓN 2

Ediciones sugeridas por Mike de Klerk (ver comentario en el fragmento de código 1st para insertar punto):

// 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);
}

Vea el comentario de ToolmakerSteve a continuación para las preocupaciones sobre esta sugerencia.

 119
Author: Olivier Jacot-Descombes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-02 13:04:48

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
 128
Author: Lee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-18 18:55:11

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 he tenido este enfoque fallarme, así que no veo ninguna razón para complicar mi código con una comprobación de la propiedad InvokeRequired.

Espero que esto ayude.

 33
Author: Matt Davis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-13 08:05:12

Crea un ThreadSafeInvoke.archivo de fragmento, y luego solo puede seleccionar las instrucciones de actualización, haga clic derecho y seleccione ' Envolvente 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>
 9
Author: Aaron Gage,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-02-15 13:42:56

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 cast que es mucho más legible, mientras que el delegado dedicado proporciona eficiencia.

progressBar1.InvokeIfRequired(o => 
{
    o.Style = ProgressBarStyle.Marquee;
    o.MarqueeAnimationSpeed = 40;
});
 6
Author: gxtaillon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-07 17:50:04

Prefiero usar una sola instancia de un delegado de método en lugar de crear una nueva instancia cada vez. En mi caso, solía mostrar mensajes de progreso y (info / error) de un Backroundworker copiando y emitiendo grandes datos desde una instancia sql. Everywhile después de cerca de 70000 llamadas de progreso y mensajes mi formulario dejó de funcionar y mostrar nuevos mensajes. Esto no ocurrió cuando empecé a usar un único 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);
}
 3
Author: stephan Schmuck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-21 11:29:00

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);
        }
    }
}
 3
Author: Konstantin S.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-26 11:17:13

Me gusta hacerlo un poco diferente, me gusta llamar "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, el IsFormClosing es un campo que establezco en True cuando cierro mi formulario, ya que podría haber algunos subprocesos en segundo plano que aún se están ejecutando...

 0
Author: Walter Verhoeven,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-21 22:07:11

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 parece a este, entonces su aplicación no es segura para subprocesos. Significa que tienes código que ya está llamando a DoGUISwitch () desde un hilo diferente. Es demasiado tarde para estar comprobando si está en un hilo diferente. InvokeRequire debe ser llamado ANTES de hacer una llamada a DoGUISwitch. No debes acceder a ningún método o propiedad desde un hilo diferente.

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

Además de la propiedad InvokeRequired, hay cuatro métodos en un control que es seguro llamar a subprocesos: Invoke, BeginInvoke, EndInvoke y CreateGraphics si la manija para el control ha sido ya crear.

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

 -3
Author: Steve Wood,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-29 13:10:30