2010-03-02 9 views
160

me han hecho dolorosamente consciente de cuán a menudo uno tiene que escribir el siguiente patrón de código en el código de interfaz gráfica de usuario basada en eventos, dondeautomatizar el patrón de código InvokeRequired

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 hace esta comprobación sin tener que pasar por todo este trabajo adicional, como un acceso directo de tipo object1.InvokeIfNecessary.visible = true.

Anterior answers han discutido la impracticabilidad de simplemente llamar a Invoke() cada vez, e incluso entonces la sintaxis de invocación() es ineficiente y todavía difícil de tratar.

Entonces, ¿alguien ha descubierto algún atajo?

+2

me he preguntado lo mismo, pero en lo que respecta a Dispatcher.CheckAccess de WPF(). –

+0

Un amigo mío señaló esto: http://www.manitra.net/blog/dev/invokerequiredinvoke-easier/ –

+0

Pensé en una sugerencia bastante loca inspirada en tu línea 'object1.InvokeIfNecessary.Visible = true'; mira mi respuesta actualizada y déjame saber lo que piensas. –

Respuesta

115

enfoque de Lee puede haber 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 como esto

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

no hay necesidad de t o pasa el control como parámetro al delegado. C# crea automáticamente un closure.


ACTUALIZACIÓN:

Según varios otros posters 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 objeto para el Invoke método como lista de parámetros para action.


ACTUALIZACIÓN 2

Las modificaciones sugeridas por Mike De Klerk (véase el comentario en 1ª 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); 
} 

comentario de Ver ToolmakerSteve por debajo de la preocupación por esta sugerencia.

+1

¿No sería mejor tener 'ISynchronizeInvoke' en lugar de' Control'? (Felicitaciones a Jon Skeet http://stackoverflow.com/questions/711408/best-way-to-invoke-any-cross-threaded-code/711414#711414) – Odys

+0

@odyodyodys: Buen punto. No sabía acerca de 'ISynchronizeInvoke'. Pero el único tipo que se deriva de él (de acuerdo con Reflector) es 'Control', por lo que la ventaja es limitada. –

+0

Nota: si va con 'ISynchronizeInvoke', _no_ podrá simplemente usar' control.Invoke (acción); ', ya que no hay una versión de un solo parámetro. Solo 'Control' ofrece eso. – DonBoitnott

126

Se puede 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 utilizar de esta manera:

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

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

public static void InvokeIfRequired<T>(this T c, Action<T> action) 
    where T : Control 
+5

Eso es un buen truco. Definitivamente lo estoy robando. –

+0

Tal vez soy demasiado tonto, pero este código no se compilará. Así que lo arreglé tal como lo construí yo (VS2008). – Oliver

+4

Para completar: en WPF hay un mecanismo de envío diferente, pero funciona de manera bastante análoga. Se podría utilizar este método de extensión allí: public static void InvokeIfRequired (este T aDestino, Acción aActionToExecute) donde T: DispatcherObject { si (aTarget.CheckAccess()) { aActionToExecute (aDestino); } else { aTarget.Dispatcher.Invoke (aActionToExecute); } } –

32

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 de blog here. No me ha fallado este enfoque, así que no veo ninguna razón para complicar mi código con una verificación de la propiedad InvokeRequired.

Espero que esto ayude.

+0

+1 - Me tropecé con la misma entrada de blog que hiciste, y creo que este es el enfoque más limpio de cualquier –

+3

propuesto. Hay un pequeño golpe de rendimiento con este enfoque, que podría acumularse cuando se lo llama varias veces. http://stackoverflow.com/a/747218/724944 – surfen

+4

Debe usar 'InvokeRequired' si el código podría ejecutarse antes de que se muestre el control o si tendrá una excepción fatal. – 56ka

9

Crear un archivo ThreadSafeInvoke.snippet, y entonces sólo puede seleccionar las instrucciones de actualización, haga clic derecho y seleccione 'Surround 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> 
3

Prefiero usar una instancia única de un método Delegado en lugar de crear una nueva instancia cada vez. En mi caso, solía mostrar el progreso y mensajes (información/error) de un Backroundworker copiando y lanzando datos de gran tamaño desde una instancia sql. Cada vez después de aproximadamente 70000 llamadas de mensajes y progreso, mi formulario dejó de funcionar y mostraba mensajes nuevos. 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); 
} 
0

Nunca se debe escribir código que se parece a esto:

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

Si tiene código que se parece a esto, entonces su solicitud no es seguro para subprocesos. Significa que tiene un código que ya está llamando a DoGUISwitch() desde un hilo diferente. Es muy tarde para verificar si se trata de un hilo diferente. InvokeRequire debe ser llamado ANTES de realizar una llamada a DoGUISwitch. No debe acceder a ningún método o propiedad desde un hilo diferente.

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

Además de la propiedad InvokeRequired, hay cuatro métodos en un control que es seguro para subprocesos llamar: Invocar, BeginInvoke, EndInvoke y CreateGraphics si el identificador para el control ya se ha creado .

En una única arquitectura de CPU no hay ningún problema, pero en una arquitectura multi-CPU puede hacer que parte del hilo de interfaz de usuario que se asignará al procesador, donde el código de llamada estaba corriendo ... y si ese es el procesador diferente de donde se estaba ejecutando el subproceso de interfaz de usuario, cuando finaliza el hilo de llamada Windows pensará que el subproceso de interfaz de usuario ha finalizado y matará el proceso de aplicación, es decir, su aplicación se cerrará sin error.

+0

Hola, gracias por su 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ías explicar un poco más. Los documentos con los que se vinculó se refieren a un peligro específico de invocar 'invoke()' y otros antes de que el control tenga un identificador, pero en mi humilde opinión no describe lo que ha descrito. El objetivo de todo esto 'invoke()' absurdo es actualizar la interfaz de usuario de una manera segura de subprocesos, y yo pensaría que poner * más * instrucciones en un contexto de bloqueo llevaría a la tartamudez? (Uf ... me alegro de haber dejado de usar M $ tech. ¡Tan complicado!) –

+0

También quiero señalar que a pesar del uso frecuente del código original (hace mucho tiempo), no observé el problema que describiste en mi CPU dual desktop –

+3

Dudo que esta respuesta sea precisa, ya que MSDN muestra muchos ejemplos como el OP. –

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

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

me gusta hacerlo un poco diferente, me gusta llamar "yo" 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 

esto es un patrón muy útil, el IsFormClosing es un campo que i se define como true cuando estoy cerrando mi forma ya que puede haber algunos temas de fondo que todavía se están ejecutando ...

1
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 => 
     obj.InvokeRequired ? (TOut)obj.Invoke(func, new object[] { obj }) : func(obj); 
} 
Cuestiones relacionadas