Automatizando el patrón de código InvokeRequired

Me he vuelto dolorosamente consciente de cuán a menudo uno necesita 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 hace esta comprobación sin tener que pasar por todo este trabajo adicional, como un object1.InvokeIfNecessary.visible = true atajo de tipo object1.InvokeIfNecessary.visible = true .

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

Entonces, ¿alguien ha descubierto algún atajo?

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 .


ACTUALIZAR :

Según varios otros carteles, el 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 lista de parámetros para la action .


ACTUALIZACIÓN 2

Ediciones sugeridas por Mike de Klerk (vea el comentario en el 1er fragmento de código para insertar el 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); } 

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

Puedes escribir un método de extensión:

 public static void InvokeIfRequired(this Control c, Action 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 puedes cambiar la firma a:

 public static void InvokeIfRequired(this T c, Action action) where T : Control 

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

Espero que esto ayude.

Cree un archivo ThreadSafeInvoke.snippet, y luego solo puede seleccionar las declaraciones de actualización, haga clic con el botón derecho y seleccione ‘Surround With …’ o Ctrl-K + S:

 < ?xml version="1.0" encoding="utf-8" ?>  
ThreadsafeInvoke Wraps code in an anonymous method passed to Invoke for Thread safety. SurroundsWith
< ![CDATA[ Invoke( (MethodInvoker) delegate { $selected$ }); ]]>

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

 public delegate void InvokeIfRequiredDelegate(T obj) where T : ISynchronizeInvoke; public static void InvokeIfRequired(this T obj, InvokeIfRequiredDelegate 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; }); 

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

Uso:

 control.InvokeIfRequired(c => c.Visible = false); return control.InvokeIfRequired(c => { c.Visible = value return c.Visible; }); 

Código:

 public static class SynchronizeInvokeExtensions { public static void InvokeIfRequired(this T obj, Action action) where T : ISynchronizeInvoke { if (obj.InvokeRequired) obj.Invoke(action, new object[] { obj }); else action(obj); } public static TOut InvokeIfRequired(this TIn obj, Func func) where TIn : ISynchronizeInvoke => obj.InvokeRequired ? (TOut)obj.Invoke(func, new object[] { obj }) : func(obj); } 

Me gusta hacer un poco diferente, me gusta llamarme “yo 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 el formulario, ya que puede haber algunos hilos de fondo que aún se estén ejecutando …

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 esto, 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 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: propiedad Control.InvokeRequired 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 ya se ha creado el identificador para el control.

En una sola architecture de CPU no hay problema, pero en una architecture de varias CPU puede hacer que parte del subproceso de UI se asigne al procesador donde se estaba ejecutando el código de llamada … y si ese procesador es diferente de donde el subproceso de interfaz de usuario se estaba ejecutando cuando termina el hilo que llama Windows pensará que el hilo de la interfaz de usuario ha terminado y matará el proceso de la aplicación, es decir, su aplicación se cerrará sin error.