una traves subprocesos subproceso que para otro net llamó llamar interfaz hilos ejemplo diferente desde delegados controles control como checkforillegalcrossthreadcalls aplicación aplanó acceder c# .net multithreading winforms user-interface

c# - traves - ¿Cómo actualizo la GUI desde otro hilo?



invokerequired vb net (30)

¿Cuál es la forma más sencilla de actualizar una Label desde otro hilo?

Tengo un Form en thread1 , y a partir de eso estoy empezando otro thread ( thread2 ). Mientras que thread2 está procesando algunos archivos, me gustaría actualizar una Label en el Form con el estado actual del thread2 de thread2 .

¿Cómo puedo hacer eso?


Manejando trabajo largo

Desde .NET 4.5 y C # 5.0 , debe usar un patrón asíncrono basado en tareas (TAP) junto con async . await palabras clave en todas las áreas (incluida la GUI):

TAP es el patrón de diseño asíncrono recomendado para nuevos desarrollos.

en lugar del Modelo de programación asíncrono (APM) y el Patrón asíncrono basado en eventos (EAP) (este último incluye la clase BackgroundWorker ).

Entonces, la solución recomendada para nuevos desarrollos es:

  1. Implementación asíncrona de un controlador de eventos (Sí, eso es todo):

    private async void Button_Clicked(object sender, EventArgs e) { var progress = new Progress<string>(s => label.Text = s); await Task.Factory.StartNew(() => SecondThreadConcern.LongWork(progress), TaskCreationOptions.LongRunning); label.Text = "completed"; }

  2. Implementación del segundo hilo que notifica el hilo de la interfaz de usuario:

    class SecondThreadConcern { public static void LongWork(IProgress<string> progress) { // Perform a long running work... for (var i = 0; i < 10; i++) { Task.Delay(500).Wait(); progress.Report(i.ToString()); } } }

Note lo siguiente:

  1. Código corto y limpio escrito de manera secuencial sin devoluciones de llamada y subprocesos explícitos.
  2. Task lugar de Thread .
  3. async clave async , que permite utilizar await que a su vez evita que el controlador de eventos alcance el estado de finalización hasta que la tarea finalice y, mientras tanto, no bloquee el subproceso de la interfaz de usuario.
  4. Clase de progreso (ver Interfaz de IProgress ) que admite el principio de diseño de Separación de Preocupaciones (SoC) y no requiere un despachador explícito ni invocación. Utiliza el actual SynchronizationContext desde su lugar de creación (aquí, el subproceso de la interfaz de usuario).
  5. TaskCreationOptions.LongRunning que sugiere no hacer cola la tarea en ThreadPool .

Para ver ejemplos más detallados, vea: El futuro de C #: Las cosas buenas llegan a aquellos que "esperan" de Joseph Albahari .

Véase también sobre el concepto UI Threading Model .

Manejo de excepciones

El siguiente fragmento de código es un ejemplo de cómo manejar las excepciones y cambiar la propiedad Enabled del botón para evitar múltiples clics durante la ejecución en segundo plano.

private async void Button_Click(object sender, EventArgs e) { button.Enabled = false; try { var progress = new Progress<string>(s => button.Text = s); await Task.Run(() => SecondThreadConcern.FailingWork(progress)); button.Text = "Completed"; } catch(Exception exception) { button.Text = "Failed: " + exception.Message; } button.Enabled = true; } class SecondThreadConcern { public static void FailingWork(IProgress<string> progress) { progress.Report("I will fail in..."); Task.Delay(500).Wait(); for (var i = 0; i < 3; i++) { progress.Report((3 - i).ToString()); Task.Delay(500).Wait(); } throw new Exception("Oops..."); } }


Cuando encontré el mismo problema, busqué la ayuda de Google, pero en lugar de darme una solución simple, me confundió más dando ejemplos de MethodInvoker y blah blah blah. Así que decidí resolverlo por mi cuenta. Aquí está mi solución:

Haz un delegado como este:

Public delegate void LabelDelegate(string s); void Updatelabel(string text) { if (label.InvokeRequired) { LabelDelegate LDEL = new LabelDelegate(Updatelabel); label.Invoke(LDEL, text); } else label.Text = text }

Puedes llamar a esta función en un nuevo hilo como este.

Thread th = new Thread(() => Updatelabel("Hello World")); th.start();

No se confunda con Thread(() => .....). Utilizo una función anónima o una expresión lambda cuando trabajo en un hilo. Para reducir las líneas de código, también puede utilizar el ThreadStart(..)método que no debo explicar aquí.


Debido a la trivialidad del escenario, tendría el sondeo de la interfaz de usuario para el estado. Creo que encontrarás que puede ser bastante elegante.

public class MyForm : Form { private volatile string m_Text = ""; private System.Timers.Timer m_Timer; private MyForm() { m_Timer = new System.Timers.Timer(); m_Timer.SynchronizingObject = this; m_Timer.Interval = 1000; m_Timer.Elapsed += (s, a) => { MyProgressLabel.Text = m_Text; }; m_Timer.Start(); var thread = new Thread(WorkerThread); thread.Start(); } private void WorkerThread() { while (...) { // Periodically publish progress information. m_Text = "Still working..."; } } }

El enfoque evita la operación de ISynchronizeInvoke.Invoke ISynchronizeInvoke.BeginInvoke requerida cuando se utilizan los métodos ISynchronizeInvoke.Invoke e ISynchronizeInvoke.BeginInvoke . No hay nada de malo en utilizar la técnica de cálculo, pero hay que tener en cuenta algunas advertencias.

  • Asegúrese de no llamar a BeginInvoke demasiada frecuencia o podría sobrepasar el mensaje de bombeo.
  • Llamar a Invoke en el subproceso de trabajo es una llamada de bloqueo. Parará temporalmente el trabajo que se está haciendo en ese hilo.

La estrategia que propongo en esta respuesta invierte los roles de comunicación de los hilos. En lugar de que el subproceso de trabajo empuje los datos, el subproceso de la interfaz de usuario lo busca Este es un patrón común utilizado en muchos escenarios. Dado que todo lo que desea hacer es mostrar información de progreso desde el subproceso de trabajo, creo que encontrará que esta solución es una excelente alternativa a la solución de cálculo de referencias. Tiene las siguientes ventajas.

  • La UI y los subprocesos de trabajo permanecen ligeramente acoplados en lugar de los enfoques Control.Invoke o Control.BeginInvoke que los acoplan estrechamente.
  • El subproceso de interfaz de usuario no impedirá el progreso del subproceso de trabajo.
  • El subproceso de trabajo no puede dominar el tiempo que el subproceso de la interfaz de usuario pasa actualizándose.
  • Los intervalos en los que la UI y los subprocesos de trabajo realizan operaciones pueden permanecer independientes.
  • El subproceso de trabajo no puede desbordar la bomba de mensajes del subproceso de la interfaz de usuario.
  • El subproceso de la interfaz de usuario determina cuándo y con qué frecuencia se actualiza la interfaz de usuario.

Dispara y olvida el método de extensión para .NET 3.5+

using System; using System.Windows.Forms; public static class ControlExtensions { /// <summary> /// Executes the Action asynchronously on the UI thread, does not block execution on the calling thread. /// </summary> /// <param name="control"></param> /// <param name="code"></param> public static void UIThread(this Control @this, Action code) { if (@this.InvokeRequired) { @this.BeginInvoke(code); } else { code.Invoke(); } } }

Esto se puede llamar usando la siguiente línea de código:

this.UIThread(() => this.myLabel.Text = "Text Goes Here");


El código de subprocesamiento es a menudo defectuoso y siempre es difícil de probar. No necesita escribir código de subprocesos para actualizar la interfaz de usuario desde una tarea en segundo plano. Simplemente use la clase BackgroundWorker para ejecutar la tarea y su método ReportProgress para actualizar la interfaz de usuario. Por lo general, solo se informa un porcentaje completado, pero hay otra sobrecarga que incluye un objeto de estado. Aquí hay un ejemplo que solo informa un objeto de cadena:

private void button1_Click(object sender, EventArgs e) { backgroundWorker1.WorkerReportsProgress = true; backgroundWorker1.RunWorkerAsync(); } private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { Thread.Sleep(5000); backgroundWorker1.ReportProgress(0, "A"); Thread.Sleep(5000); backgroundWorker1.ReportProgress(0, "B"); Thread.Sleep(5000); backgroundWorker1.ReportProgress(0, "C"); } private void backgroundWorker1_ProgressChanged( object sender, ProgressChangedEventArgs e) { label1.Text = e.UserState.ToString(); }

Eso está bien si siempre quieres actualizar el mismo campo. Si tiene que hacer actualizaciones más complicadas, puede definir una clase para representar el estado de la interfaz de usuario y pasarla al método ReportProgress.

Una última cosa, asegúrese de establecer el indicador WorkerReportsProgress , o el método ReportProgress se ignorará por completo.


Esta es la forma clásica en que debes hacer esto:

using System; using System.Windows.Forms; using System.Threading; namespace Test { public partial class UIThread : Form { Worker worker; Thread workerThread; public UIThread() { InitializeComponent(); worker = new Worker(); worker.ProgressChanged += new EventHandler<ProgressChangedArgs>(OnWorkerProgressChanged); workerThread = new Thread(new ThreadStart(worker.StartWork)); workerThread.Start(); } private void OnWorkerProgressChanged(object sender, ProgressChangedArgs e) { // Cross thread - so you don''t get the cross-threading exception if (this.InvokeRequired) { this.BeginInvoke((MethodInvoker)delegate { OnWorkerProgressChanged(sender, e); }); return; } // Change control this.label1.Text = e.Progress; } } public class Worker { public event EventHandler<ProgressChangedArgs> ProgressChanged; protected void OnProgressChanged(ProgressChangedArgs e) { if(ProgressChanged!=null) { ProgressChanged(this,e); } } public void StartWork() { Thread.Sleep(100); OnProgressChanged(new ProgressChangedArgs("Progress Changed")); Thread.Sleep(100); } } public class ProgressChangedArgs : EventArgs { public string Progress {get;private set;} public ProgressChangedArgs(string progress) { Progress = progress; } } }

Tu hilo de trabajador tiene un evento. Su hilo de interfaz de usuario comienza con otro hilo para hacer el trabajo y conecta ese evento de trabajo para que pueda mostrar el estado del hilo de trabajo.

Luego, en la interfaz de usuario debe cruzar hilos para cambiar el control real ... como una etiqueta o una barra de progreso.


Esta es similar a la solución anterior utilizando .NET Framework 3.0, pero resolvió el problema del soporte de seguridad en tiempo de compilación .

public static class ControlExtension { delegate void SetPropertyValueHandler<TResult>(Control souce, Expression<Func<Control, TResult>> selector, TResult value); public static void SetPropertyValue<TResult>(this Control source, Expression<Func<Control, TResult>> selector, TResult value) { if (source.InvokeRequired) { var del = new SetPropertyValueHandler<TResult>(SetPropertyValue); source.Invoke(del, new object[]{ source, selector, value}); } else { var propInfo = ((MemberExpression)selector.Body).Member as PropertyInfo; propInfo.SetValue(source, value, null); } } }

Usar:

this.lblTimeDisplay.SetPropertyValue(a => a.Text, "some string"); this.lblTimeDisplay.SetPropertyValue(a => a.Visible, false);

El compilador fallará si el usuario pasa el tipo de datos incorrecto.

this.lblTimeDisplay.SetPropertyValue(a => a.Visible, "sometext");


Esto en mi variación C # 3.0 de la solución de Ian Kemp:

public static void SetPropertyInGuiThread<C,V>(this C control, Expression<Func<C, V>> property, V value) where C : Control { var memberExpression = property.Body as MemberExpression; if (memberExpression == null) throw new ArgumentException("The ''property'' expression must specify a property on the control."); var propertyInfo = memberExpression.Member as PropertyInfo; if (propertyInfo == null) throw new ArgumentException("The ''property'' expression must specify a property on the control."); if (control.InvokeRequired) control.Invoke( (Action<C, Expression<Func<C, V>>, V>)SetPropertyInGuiThread, new object[] { control, property, value } ); else propertyInfo.SetValue(control, value, null); }

Lo llamas así:

myButton.SetPropertyInGuiThread(b => b.Text, "Click Me!")

  1. Añade una comprobación nula al resultado de "como MemberExpression".
  2. Mejora la seguridad de tipo estático.

De lo contrario, el original es una solución muy agradable.


La forma más sencilla es un método anónimo que se pasa a Label.Invoke :

// Running on the worker thread string newText = "abc"; form.Label.Invoke((MethodInvoker)delegate { // Running on the UI thread form.Label.Text = newText; }); // Back on the worker thread

Observe que Invoke bloquea la ejecución hasta que se complete, esto es un código síncrono. La pregunta no se refiere al código asíncrono, pero hay un montón de contenido en Desbordamiento de pila sobre cómo escribir código asíncrono cuando desea obtener más información.


La gran mayoría de las respuestas usa Control.Invoke que es una condición de carrera que está por ocurrir . Por ejemplo, considere la respuesta aceptada:

string newText = "abc"; // running on worker thread this.Invoke((MethodInvoker)delegate { someLabel.Text = newText; // runs on UI thread });

Si el usuario cierra el formulario justo antes de this.Invoke Se llama a this.Invoke (recuerde, this es el objeto de Form ), ObjectDisposedException se ObjectDisposedException una ObjectDisposedException .

La solución es utilizar SynchronizationContext , específicamente SynchronizationContext.Current como sugiere hamilton.danielb (otras respuestas se basan en implementaciones específicas de SynchronizationContext que son completamente innecesarias). Sin embargo, modificaría ligeramente su código para usar SynchronizationContext.Post lugar de SynchronizationContext.Send (ya que normalmente no es necesario que el subproceso de trabajo espere):

public partial class MyForm : Form { private readonly SynchronizationContext _context; public MyForm() { _context = SynchronizationContext.Current ... } private MethodOnOtherThread() { ... _context.Post(status => someLabel.Text = newText,null); } }

Tenga en cuenta que en .NET 4.0 y superiores debería estar usando tareas para operaciones asíncronas. Vea n-san''s respuesta de n-san''s para el enfoque equivalente basado en tareas (utilizando TaskScheduler.FromCurrentSynchronizationContext ).

Finalmente, en .NET 4.5 y superiores, también puede usar Progress<T> (que básicamente captura SynchronizationContext.Current sobre su creación) como lo demostró Ryszard Dżegan''s para los casos en que la operación de ejecución prolongada necesita ejecutar el código UI mientras aún funciona.


La solución simple es usar Control.Invoke .

void DoSomething() { if (InvokeRequired) { Invoke(new MethodInvoker(updateGUI)); } else { // Do Something updateGUI(); } } void updateGUI() { // update gui here }


Ninguna de las cosas Invocar en las respuestas anteriores es necesaria.

Necesitas mirar WindowsFormsSynchronizationContext:

// In the main thread WindowsFormsSynchronizationContext mUiContext = new WindowsFormsSynchronizationContext(); ... // In some non-UI Thread // Causes an update in the GUI thread. mUiContext.Post(UpdateGUI, userData); ... void UpdateGUI(object userData) { // Update your GUI controls here }


Para .NET 2.0, aquí hay una buena parte del código que escribí que hace exactamente lo que quieres, y funciona para cualquier propiedad en un Control :

private delegate void SetControlPropertyThreadSafeDelegate( Control control, string propertyName, object propertyValue); public static void SetControlPropertyThreadSafe( Control control, string propertyName, object propertyValue) { if (control.InvokeRequired) { control.Invoke(new SetControlPropertyThreadSafeDelegate (SetControlPropertyThreadSafe), new object[] { control, propertyName, propertyValue }); } else { control.GetType().InvokeMember( propertyName, BindingFlags.SetProperty, null, control, new object[] { propertyValue }); } }

Llámalo así:

// thread-safe equivalent of // myLabel.Text = status; SetControlPropertyThreadSafe(myLabel, "Text", status);

Si está utilizando .NET 3.0 o superior, puede volver a escribir el método anterior como un método de extensión de la clase Control , lo que simplificaría la llamada a:

myLabel.SetPropertyThreadSafe("Text", status);

ACTUALIZACIÓN 05/10/2010:

Para .NET 3.0 debes usar este código:

private delegate void SetPropertyThreadSafeDelegate<TResult>( Control @this, Expression<Func<TResult>> property, TResult value); public static void SetPropertyThreadSafe<TResult>( this Control @this, Expression<Func<TResult>> property, TResult value) { var propertyInfo = (property.Body as MemberExpression).Member as PropertyInfo; if (propertyInfo == null || [email protected]().IsSubclassOf(propertyInfo.ReflectedType) || @this.GetType().GetProperty( propertyInfo.Name, propertyInfo.PropertyType) == null) { throw new ArgumentException("The lambda expression ''property'' must reference a valid property on this Control."); } if (@this.InvokeRequired) { @this.Invoke(new SetPropertyThreadSafeDelegate<TResult> (SetPropertyThreadSafe), new object[] { @this, property, value }); } else { @this.GetType().InvokeMember( propertyInfo.Name, BindingFlags.SetProperty, null, @this, new object[] { value }); } }

que utiliza expresiones LINQ y lambda para permitir una sintaxis mucho más clara, más sencilla y más segura:

myLabel.SetPropertyThreadSafe(() => myLabel.Text, status); // status has to be a string or this will fail to compile

No solo se comprueba el nombre de la propiedad en el momento de la compilación, sino también el tipo de propiedad, por lo que es imposible (por ejemplo) asignar un valor de cadena a una propiedad booleana y, por lo tanto, provocar una excepción de tiempo de ejecución.

Desafortunadamente, esto no impide que nadie haga cosas estúpidas, como pasar la propiedad y el valor de otro Control , por lo que lo siguiente compilará con gusto:

myLabel.SetPropertyThreadSafe(() => aForm.ShowIcon, false);

Por lo tanto, agregué los controles de tiempo de ejecución para asegurar que la propiedad pasada en realidad pertenece al Control que se está solicitando el método. No es perfecto, pero sigue siendo mucho mejor que la versión .NET 2.0.

Si alguien tiene más sugerencias sobre cómo mejorar este código para la seguridad en tiempo de compilación, ¡por favor comente!


Para muchos propósitos es tan simple como esto:

public delegate void serviceGUIDelegate(); private void updateGUI() { this.Invoke(new serviceGUIDelegate(serviceGUI)); }

"serviceGUI ()" es un método de nivel de GUI dentro del formulario (esto) que puede cambiar tantos controles como desee. Llame a "updateGUI ()" desde el otro hilo. Se pueden agregar parámetros para pasar valores, o (probablemente más rápido) usar variables de alcance de clase con bloqueos según sea necesario si existe alguna posibilidad de un conflicto entre los hilos que acceden a ellos que podría causar inestabilidad. Use BeginInvoke en lugar de Invoke si el subproceso que no es de GUI es crítico en el tiempo (teniendo en cuenta la advertencia de Brian Gideon).


Salvete Después de haber buscado esta pregunta, encontré que las respuestas de FrankG y Oregon Ghost son las más fáciles y útiles para mí. Ahora codifico en Visual Basic y ejecuté este fragmento a través de un convertidor; así que no estoy seguro de cómo resulta.

Tengo un formulario de diálogo llamado form_Diagnostics, que tiene un cuadro de texto enriquecido, llamado updateDiagWindow, que estoy usando como una especie de pantalla de registro. Necesitaba poder actualizar su texto de todos los hilos. Las líneas adicionales permiten que la ventana se desplace automáticamente a las líneas más nuevas.

Y así, ahora puedo actualizar la pantalla con una línea, desde cualquier lugar en todo el programa, de la forma que creas que funcionaría sin ningún tipo de subprocesos:

form_Diagnostics.updateDiagWindow(whatmessage);

Código principal (ponga esto dentro del código de clase de su formulario):

#region "---------Update Diag Window Text------------------------------------" // This sub allows the diag window to be updated by all threads public void updateDiagWindow(string whatmessage) { var _with1 = diagwindow; if (_with1.InvokeRequired) { _with1.Invoke(new UpdateDiagDelegate(UpdateDiag), whatmessage); } else { UpdateDiag(whatmessage); } } // This next line makes the private UpdateDiagWindow available to all threads private delegate void UpdateDiagDelegate(string whatmessage); private void UpdateDiag(string whatmessage) { var _with2 = diagwindow; _with2.appendtext(whatmessage); _with2.SelectionStart = _with2.Text.Length; _with2.ScrollToCaret(); } #endregion


Tendrá que asegurarse de que la actualización suceda en el hilo correcto; el hilo de la interfaz de usuario.

Para hacer esto, tendrás que invocar el controlador de eventos en lugar de llamarlo directamente.

Puedes hacer esto levantando tu evento de esta manera:

(El código se escribe aquí fuera de mi cabeza, por lo que no he comprobado la sintaxis correcta, etc., pero debería comenzar).

if( MyEvent != null ) { Delegate[] eventHandlers = MyEvent.GetInvocationList(); foreach( Delegate d in eventHandlers ) { // Check whether the target of the delegate implements // ISynchronizeInvoke (Winforms controls do), and see // if a context-switch is required. ISynchronizeInvoke target = d.Target as ISynchronizeInvoke; if( target != null && target.InvokeRequired ) { target.Invoke (d, ... ); } else { d.DynamicInvoke ( ... ); } } }

Tenga en cuenta que el código anterior no funcionará en los proyectos de WPF, ya que los controles de WPF no implementan la interfaz ISynchronizeInvoke .

Para asegurarse de que el código anterior funciona con Windows Forms y WPF, y todas las demás plataformas, puede echar un vistazo a las AsyncOperation , AsyncOperationManager y SynchronizationContext .

Para poder generar eventos fácilmente de esta manera, he creado un método de extensión, que me permite simplificar el evento simplemente llamando:

MyEvent.Raise(this, EventArgs.Empty);

Por supuesto, también puede hacer uso de la clase BackGroundWorker, que resumirá este asunto por usted.


Tendrá que invocar el método en el hilo GUI. Puedes hacerlo llamando a Control.Invoke.

Por ejemplo:

delegate void UpdateLabelDelegate (string message); void UpdateLabel (string message) { if (InvokeRequired) { Invoke (new UpdateLabelDelegate (UpdateLabel), message); return; } MyLabelControl.Text = message; }



Crear una variable de clase:

SynchronizationContext _context;

Establézcalo en el constructor que crea su interfaz de usuario:

var _context = SynchronizationContext.Current;

Cuando quieras actualizar la etiqueta:

_context.Send(status =>{ // UPDATE LABEL }, null);


Debes utilizar invocar y delegar.

private delegate void MyLabelDelegate(); label1.Invoke( new MyLabelDelegate(){ label1.Text += 1; });


Intenta actualizar la etiqueta usando este

public static class ExtensionMethods { private static Action EmptyDelegate = delegate() { }; public static void Refresh(this UIElement uiElement) { uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate); } }


La forma más fácil que pienso:

void Update() { BeginInvoke((Action)delegate() { //do your update }); }


Por ejemplo, acceder a un control que no sea en el hilo actual:

Speed_Threshold = 30; textOutput.Invoke(new EventHandler(delegate { lblThreshold.Text = Speed_Threshold.ToString(); }));

Ahí lblThresholdhay una etiqueta y Speed_Thresholdes una variable global.


Simplemente usa algo como esto:

this.Invoke((MethodInvoker)delegate { progressBar1.Value = e.ProgressPercentage; // runs on UI thread });


Acabo de leer las respuestas y esto parece ser un tema muy candente. Actualmente estoy usando .NET 3.5 SP1 y Windows Forms.

La conocida fórmula ampliamente descrita en las respuestas anteriores que hace uso de la propiedad InvokeRequired cubre la mayoría de los casos, pero no la agrupación completa.

¿Qué pasa si el Mango no ha sido creado todavía?

La propiedad InvokeRequired , como se describe aquí (Control.InvokeRequired Property reference to MSDN) devuelve true si la llamada se realizó desde un subproceso que no es el subproceso de la GUI, false si la llamada se realizó desde el subproceso de la GUI, o si el identificador era aún no creado.

Puede encontrar una excepción si desea que otro hilo muestre y actualice un formulario modal. Debido a que desea que la forma se muestre de manera modal, podría hacer lo siguiente:

private MyForm _gui; public void StartToDoThings() { _gui = new MyForm(); Thread thread = new Thread(SomeDelegate); thread.Start(); _gui.ShowDialog(); }

Y el delegado puede actualizar una etiqueta en la GUI:

private void SomeDelegate() { // Operations that can take a variable amount of time, even no time //... then you update the GUI if(_gui.InvokeRequired) _gui.Invoke((Action)delegate { _gui.Label1.Text = "Done!"; }); else _gui.Label1.Text = "Done!"; }

Esto puede causar una InvalidOperationException si las operaciones antes de la actualización de la etiqueta "toman menos tiempo" (leerlo e interpretarlo como una simplificación) que el tiempo que tarda el hilo de interfaz gráfica de usuario para crear el formulario ''s de la manija . Esto sucede dentro del método ShowDialog () .

También deberías revisar el Mango de esta manera:

private void SomeDelegate() { // Operations that can take a variable amount of time, even no time //... then you update the GUI if(_gui.IsHandleCreated) // <---- ADDED if(_gui.InvokeRequired) _gui.Invoke((Action)delegate { _gui.Label1.Text = "Done!"; }); else _gui.Label1.Text = "Done!"; }

Puede manejar la operación que se realizará si el identificador aún no se ha creado: simplemente puede ignorar la actualización de la GUI (como se muestra en el código anterior) o puede esperar (más riesgoso). Esto debería responder a la pregunta.

Cosas opcionales: Personalmente se me ocurrió codificar lo siguiente:

public class ThreadSafeGuiCommand { private const int SLEEPING_STEP = 100; private readonly int _totalTimeout; private int _timeout; public ThreadSafeGuiCommand(int totalTimeout) { _totalTimeout = totalTimeout; } public void Execute(Form form, Action guiCommand) { _timeout = _totalTimeout; while (!form.IsHandleCreated) { if (_timeout <= 0) return; Thread.Sleep(SLEEPING_STEP); _timeout -= SLEEPING_STEP; } if (form.InvokeRequired) form.Invoke(guiCommand); else guiCommand(); } }

Alimino mis formularios que se actualizan mediante otro hilo con una instancia de este ThreadSafeGuiCommand , y defino métodos que actualizan la GUI (en mi formulario) de la siguiente manera:

public void SetLabeTextTo(string value) { _threadSafeGuiCommand.Execute(this, delegate { Label1.Text = value; }); }

De esta manera, estoy bastante seguro de que mi GUI se actualizará en cualquier subproceso que haga la llamada, opcionalmente esperando un tiempo bien definido (el tiempo de espera).


Cuando se encuentre en el subproceso de la interfaz de usuario, puede solicitar su programador de tareas de contexto de sincronización. Le daría un TaskScheduler que programa todo en el hilo de la interfaz de usuario.

Luego puede encadenar sus tareas para que cuando el resultado esté listo, otra tarea (que está programada en el subproceso de la interfaz de usuario) lo elija y lo asigne a una etiqueta.

public partial class MyForm : Form { private readonly TaskScheduler _uiTaskScheduler; public MyForm() { InitializeComponent(); _uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext(); } private void buttonRunAsyncOperation_Click(object sender, EventArgs e) { RunAsyncOperation(); } private void RunAsyncOperation() { var task = new Task<string>(LengthyComputation); task.ContinueWith(antecedent => UpdateResultLabel(antecedent.Result), _uiTaskScheduler); task.Start(); } private string LengthyComputation() { Thread.Sleep(3000); return "47"; } private void UpdateResultLabel(string text) { labelResult.Text = text; } }

Esto funciona para tareas (no hilos) que son la forma preferida de escribir código concurrente ahora .


Mi versión es insertar una línea de "mantra" recursivo:

Sin argumentos:

void Aaaaaaa() { if (InvokeRequired) { Invoke(new Action(Aaaaaaa)); return; } //1 line of mantra // Your code! }

Para una función que tiene argumentos:

void Bbb(int x, string text) { if (InvokeRequired) { Invoke(new Action<int, string>(Bbb), new[] { x, text }); return; } // Your code! }

Eso es todo .

Alguna argumentación : por lo general, es malo para la legibilidad del código poner {} después de una if ()declaración en una línea. Pero en este caso es la rutina del mismo "mantra". No rompe la legibilidad del código si este método es consistente en el proyecto. Y guarda su código de basura (una línea de código en lugar de cinco).

Como ves if(InvokeRequired) {something long}, solo sabes "esta función es segura para llamar desde otro hilo".


Puede utilizar el delegado ya existente Action:

private void UpdateMethod() { if (InvokeRequired) { Invoke(new Action(UpdateMethod)); } }


Quería agregar una advertencia porque noté que algunas de las soluciones simples omiten la InvokeRequiredverificación.

Noté que si su código se ejecuta antes de que se haya creado el controlador de ventana del control (por ejemplo, antes de que se muestre el formulario), se produce Invokeuna excepción. Así que recomiendo siempre comprobar InvokeRequiredantes de llamar Invokeo BeginInvoke.


Label lblText; //initialized elsewhere void AssignLabel(string text) { if (InvokeRequired) { BeginInvoke((Action<string>)AssignLabel, text); return; } lblText.Text = text; }

Tenga en cuenta que BeginInvoke() se prefiere a Invoke() porque es menos probable que provoque puntos muertos (sin embargo, esto no es un problema aquí cuando solo se asigna texto a una etiqueta):

Al usar Invoke() está esperando que regrese el método. Ahora, es posible que haga algo en el código invocado que deberá esperar el hilo, que puede no ser inmediatamente obvio si está oculto en algunas funciones que está llamando, lo que a su vez puede suceder indirectamente mediante los controladores de eventos. Así que estarías esperando el hilo, el hilo te estaría esperando y estarías en un punto muerto.

Esto provocó que algunos de nuestros programas lanzados se bloquearan. Fue bastante fácil de arreglar reemplazando Invoke() con BeginInvoke() . A menos que necesite una operación síncrona, que puede ser el caso si necesita un valor de retorno, use BeginInvoke() .