traves thread subprocesos llamar form example ejemplo controles como c# multithreading design invoke invokerequired

thread - invokerequired c#



Código de limpieza lleno de InvokeRequired (8)

Bueno, ¿qué tal esto?

public static class ControlHelpers { public static void InvokeIfRequired<T>(this T control, Action<T> action) where T : ISynchronizeInvoke { if (control.InvokeRequired) { control.Invoke(new Action(() => action(control)), null); } else { action(control); } } }

Úselo así:

private void UpdateSummary(string text) { summary.InvokeIfRequired(s => { s.Text = text }); }

Sé que al manipular los controles de la interfaz de usuario desde cualquier hilo que no sea UI, debe ordenar sus llamadas a la secuencia de la interfaz de usuario para evitar problemas. El consenso general es que debe usar la prueba InvokeRequired, y si es verdadera, use .Invoke para realizar la clasificación.

Esto lleva a una gran cantidad de código que se ve así:

private void UpdateSummary(string text) { if (this.InvokeRequired) { this.Invoke(new Action(() => UpdateSummary(text))); } else { summary.Text = text; } }

Mi pregunta es esta: ¿puedo omitir la prueba InvokeRequired y llamar a Invoke, así:

private void UpdateSummary(string text) { this.Invoke(new Action(() => summary.Text = text)); }

¿Hay algún problema al hacer esto? Si es así, ¿hay una mejor manera de mantener la prueba InvokeRequired sin tener que copiar y pegar este patrón por todos lados?


Es más fácil usar BackgroudWorker, si es posible, para hacer que la UI responda y use ReportProgress para actualizar la UI porque se ejecuta en el mismo hilo que la UI, por lo tanto, no necesitará InvokeRequired.


He estado leyendo acerca de los argumentos al agregar una verificación lógica para averiguar si la invocación debe usarse IFF cuando no esté en el hilo de la interfaz de usuario y no en el hilo de la interfaz de usuario. Escribí una clase que examina el tiempo para ejecutar (a través de Cronómetro ) varios métodos para obtener una estimación aproximada de la eficiencia de un método sobre otro.

Los resultados pueden sorprender a algunos de ustedes (estas pruebas se realizaron a través del evento Form.Shown ):

// notice that we are updating the form''s title bar 10,000 times // directly on the UI thread TimedAction.Go ( "Direct on UI Thread", () => { for (int i = 0; i < 10000; i++) { this.Text = "1234567890"; } } ); // notice that we are invoking the update of the title bar // (UI thread -> [invoke] -> UI thread) TimedAction.Go ( "Invoke on UI Thread", () => { this.Invoke ( new Action ( () => { for (int i = 0; i < 10000; i++) { this.Text = "1234567890"; } } ) ); } ); // the following is invoking each UPDATE on the UI thread from the UI thread // (10,000 invokes) TimedAction.Go ( "Separate Invoke on UI Thread", () => { for (int i = 0; i < 10000; i++) { this.Invoke ( new Action ( () => { this.Text = "1234567890"; } ) ); } } );

Los resultados son los siguientes:

  • TimedAction :: Go () + 0 - Depuración: [DEBUG] Cronómetro [Directo en el subproceso UI]: 300ms
  • TimedAction :: Go () + 0 - Depurar: [DEBUG] Cronómetro [Invocar en el hilo de UI]: 299ms
  • TimedAction :: Go () + 0 - Depurar: [DEBUG] Cronómetro [Invocar por separado en el hilo de la interfaz de usuario]: 649ms

Mi conclusión es que puede invocar de forma segura en cualquier momento, independientemente de si está en el hilo de la interfaz de usuario o en el hilo de trabajo, sin una sobrecarga significativa de bucles a través de la bomba de mensajes. Sin embargo, realizar la mayor parte del trabajo en el subproceso de interfaz de usuario en lugar de realizar muchas llamadas al subproceso de interfaz de usuario (a través de Invoke () ) es ventajoso y mejora la eficiencia en gran medida.


Llamando a la Invoke desde el hilo de la interfaz de usuario es algo ineficiente.

En su lugar, puede crear un método de extensión InvokeIfNeeded que tome un parámetro de Action . (esto también le permitiría eliminar una new Action(...) del callsite)


Me doy cuenta de que ya hay una respuesta bastante acertada , pero también quise publicar mi opinión (que también publiqué here ).

El mío es un poco diferente ya que puede manejar controles nulos un poco más seguros y puede devolver resultados cuando sea necesario. Ambas me han sido útiles al intentar Invocar mostrando un MessageBox en un formulario primario que podría ser nulo, y al devolver el DialogResult de mostrar ese MessageBox.

using System; using System.Windows.Forms; /// <summary> /// Extension methods acting on Control objects. /// </summary> internal static class ControlExtensionMethods { /// <summary> /// Invokes the given action on the given control''s UI thread, if invocation is needed. /// </summary> /// <param name="control">Control on whose UI thread to possibly invoke.</param> /// <param name="action">Action to be invoked on the given control.</param> public static void MaybeInvoke(this Control control, Action action) { if (control != null && control.InvokeRequired) { control.Invoke(action); } else { action(); } } /// <summary> /// Maybe Invoke a Func that returns a value. /// </summary> /// <typeparam name="T">Return type of func.</typeparam> /// <param name="control">Control on which to maybe invoke.</param> /// <param name="func">Function returning a value, to invoke.</param> /// <returns>The result of the call to func.</returns> public static T MaybeInvoke<T>(this Control control, Func<T> func) { if (control != null && control.InvokeRequired) { return (T)(control.Invoke(func)); } else { return func(); } } }

Uso:

myForm.MaybeInvoke(() => this.Text = "Hello world"); // Sometimes the control might be null, but that''s okay. var dialogResult = this.Parent.MaybeInvoke(() => MessageBox.Show(this, "Yes or no?", "Choice", MessageBoxButtons.YesNo));


Mi enfoque preferido para los controles de solo lectura es tener todo el estado de control encapsulado en una clase que se puede actualizar sin pasar por ningún estado inconsistente (una forma simple de hacerlo es poner todos los elementos que necesitan actualizarse juntos en una clase inmutable, y crea una nueva instancia de la clase siempre que se necesite una actualización). Luego, tenga un método que se enclavija. Cambie un indicador updateNeeded y, si no hay una actualización pendiente pero IsHandleCreated es verdadero, entonces BeginInvoque el procedimiento de actualización. El procedimiento de actualización debe borrar el indicador updateNeeded como la primera cosa que hace, antes de hacer cualquier actualización (si alguien intenta actualizar el control en ese punto, otra solicitud será BeginInvoked). Tenga en cuenta que debe estar preparado para atrapar y tragar una excepción (creo que IllegalOperation) si el control se elimina justo cuando se está preparando para actualizarlo.

Por cierto, si un control aún no se ha unido a un hilo (al ser agregado a una ventana visible, o al hacerse visible la ventana en la que está), es legal actualizarlo directamente, pero no legal, para usar BeginInvoke o Invoke en él.


No estoy convencido de que Control.Invoke sea ​​la mejor opción para actualizar la UI. No puedo asegurarlo en su caso porque no conozco las circunstancias bajo las cuales UpdateSummary in UpdateSummary . Sin embargo, si lo llama periódicamente como un mecanismo para mostrar información de progreso (que es la impresión que obtengo del fragmento de código), generalmente hay una mejor opción. Esa opción es tener la encuesta de hilos de la interfaz de usuario para el estado en lugar de tener el hilo de trabajo empujarlo.

Las razones por las que se debe considerar el método de votación en este caso es porque:

  • Rompe el estrecho acoplamiento entre la interfaz de usuario y los hilos de trabajo que Control.Invoke impone.
  • Pone la responsabilidad de actualizar el hilo de la interfaz de usuario en el hilo de la interfaz de usuario donde debe pertenecer de todos modos.
  • El hilo de la interfaz de usuario llega a dictar cuándo y con qué frecuencia debe llevarse a cabo la actualización.
  • No hay riesgo de que la bomba de mensajes de IU se desborde, como sería el caso con las técnicas de clasificación iniciadas por el hilo de trabajo.
  • El hilo de trabajo no tiene que esperar a que se confirme que la actualización se realizó antes de continuar con sus próximos pasos (es decir, obtiene más rendimiento tanto en la interfaz de usuario como en los hilos de trabajo).

Por lo tanto, considere crear un System.Windows.Forms.Timer que compruebe periódicamente el texto que se mostrará en el Control lugar de iniciar la inserción desde el subproceso de trabajo. Una vez más, sin conocer sus requisitos exactos, no estoy dispuesto a decir esta definitivamente la dirección que necesita ir, pero en la mayoría de los casos es mejor que la opción Control.Invoke .

Obviamente, este enfoque elimina por completo la necesidad del cheque InvokedRequired . No importa, el hecho de que simplifica todos los demás aspectos de la interacción UI / subproceso de trabajo.


No puedo comentar aún, con suerte alguien verá esto y lo agregará a la respuesta aceptada, que de otro modo es perfecta.

control.Invoke(new Action(() => action(control))); tiene que leer
control.Invoke(new Action(() => action(control)), null);

Como está escrito, la respuesta aceptada no compilará porque ISynchronizeInvoke.Invoke() no tiene una sobrecarga con solo 1 argumento como lo hace Control.Invoke() .

Otra cosa es que el uso podría ser más claro
summary.InvokeIfRequired(c => { summary.Text = text; }); en lugar de un summary.InvokeIfRequired(c => { textBox.Text = text }); escrito. summary.InvokeIfRequired(c => { textBox.Text = text });