¿Cómo puedo actualizar la GUI desde otro hilo?


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

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

¿Cómo puedo hacer eso?

Author: Uwe Keim, 2009-03-19

30 answers

Para. NET 2.0, aquí hay un buen 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, podría reescribir 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);

ACTUALIZAR 05/10/2010:

Para. NET 3.0 debe 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 LINQ y lambda expresiones para permitir una sintaxis mucho más limpia, simple y 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 tiempo de compilación, sino también el tipo de la propiedad, por lo que es imposible (por ejemplo) asignar un valor de cadena a una propiedad booleana, y por lo tanto causar una excepción de tiempo de ejecución.

Desafortunadamente esto no detiene a nadie de hacer cosas estúpidas como pasar la propiedad y el valor de otro Control, por lo que lo siguiente se compilará felizmente:

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

Por lo tanto, agregué la runtime comprueba que la propiedad passed-in realmente pertenece al Control en el que se está invocando el método. No es perfecto, pero aún así es 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!

 701
Author: Ian Kemp,
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-03-03 00:52:09

La forma más simple es un método anónimoLabel.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 completa this este es código síncrono. La pregunta no es sobre el código asincrónico, pero hay un montón de contenido en Stack Overflow sobre escribir código asincrónico cuando quieres aprender sobre ello.

 956
Author: Marc Gravell,
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-09-16 09:20:29

Manejo de trabajo largo

Ya que .NET 4.5 y C# 5.0 debería usar Tarea-modelo Asincrónico basado (TAP) junto con async-esperan palabras clave en todas las áreas (incluyendo la interfaz de usuario):

TAP es el patrón de diseño asíncrono recomendado para el nuevo desarrollo

En lugar de Modelo de Programación Asíncrona (APM) y Asíncrono basado en eventos Pattern (EAP) (este último incluye la Clase de BackgroundWorker ).

Entonces, la solución recomendada para el nuevo desarrollo 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 subproceso que notifica el subproceso UI:

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

Observe lo siguiente:

  1. Código corto y limpio escrito de manera secuencial sin devoluciones de llamada y explícito hilo.
  2. Tarea en lugar de Hilo.
  3. palabra clave async, que permite usar 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 bloquea el subproceso de la interfaz de usuario.
  4. Clase de progreso (ver Interfaz IProgress) que soporta Separación de Preocupaciones (SoC) principio de diseño y no requiere explicit dispatcher e invocación. Utiliza la corriente SynchronizationContext desde su lugar de creación (aquí el hilo de la interfaz de usuario).
  5. TaskCreationOptions.LongRunning que sugiere no encolar la tarea en ThreadPool.

Para ejemplos más detallados ver: El Futuro de C#: Las cosas buenas vienen a aquellos que 'esperan' por Joseph Albahari.

Vea también acerca del concepto del Modelo de enhebrado de interfaz de usuario .

Manejo de excepciones

El siguiente fragmento es un ejemplo de cómo para manejar excepciones y alternar 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...");
    }
}
 344
Author: Ryszard Dżegan,
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-05-23 11:55:02

Variación de La solución más simple de Marc Gravell para. NET 4:

control.Invoke((MethodInvoker) (() => control.Text = "new text"));

O usa Action delegate en su lugar:

control.Invoke(new Action(() => control.Text = "new text"));

Vea aquí una comparación de los dos: MethodInvoker vs Action for Control.BeginInvoke

 200
Author: Zaid Masud,
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-05-23 12:34:51

Método de extensión Fire and forget 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");
 117
Author: StyxRiver,
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-08-27 21:10:32

Esta es la forma clásica en que debe 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 trabajo tiene un evento. Tu subproceso de IU comienza con otro subproceso para hacer el trabajo y conecta ese evento de trabajo para que puedas mostrar el estado del subproceso de trabajo.

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

 59
Author: Hath,
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-03-26 09:45:55

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
}
 52
Author: OregonGhost,
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-12-19 10:02:10

El código de subproceso es a menudo defectuoso y siempre difícil de probar. No es necesario 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 reportas un porcentaje completo, pero hay otra sobrecarga que incluye un objeto de estado. Aquí hay un ejemplo que solo informa de un objeto string:

    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 realizar actualizaciones más complicadas, podría 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 la bandera WorkerReportsProgress, o el método ReportProgress será completamente ignorado.

 41
Author: Don Kirkby,
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
2012-09-22 03:59:50

La gran mayoría de las respuestas usan Control.Invoke que es una condición de raza esperando a suceder. 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 es llamado (recuerde, this es Form object), un ObjectDisposedException será despedido.

La solución es usar SynchronizationContext, específicamente SynchronizationContext.Current como hamilton.danielb sugiere (otras respuestas dependen de implementaciones SynchronizationContext específicas, lo cual es completamente innecesario). Lo haría sin embargo, modifique ligeramente su código para usar SynchronizationContext.Post en lugar de SynchronizationContext.Send (ya que normalmente no hay necesidad de que el hilo 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 realmente debería usar tareas para operaciones asincrónicas. Ver la respuesta de n-san para el enfoque equivalente basado en tareas (usando TaskScheduler.FromCurrentSynchronizationContext).

Finalmente, en. NET 4.5 y superiores también puede usar Progress<T> (que básicamente captura SynchronizationContext.Current en su creación) como lo demuestra Ryszard Dżegan para los casos en los que la operación de larga duración necesita ejecutar código de interfaz de usuario mientras sigue funcionando.

 32
Author: Ohad Schneider,
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-05-23 12:34:51

Tendrá que asegurarse de que la actualización se realice en el subproceso correcto; el subproceso de interfaz de usuario.

Para hacer esto, tendrás que invocar el event-handler en lugar de llamarlo directamente.

Puedes hacer esto elevando tu evento de esta manera:

(El código está escrito aquí fuera de mi cabeza, así que no he comprobado la sintaxis correcta, etc., pero debería ponerte en marcha.)

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 WPF, ya que los controles 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 la AsyncOperation, AsyncOperationManager y SynchronizationContext clases.

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

MyEvent.Raise(this, EventArgs.Empty);

Por supuesto, también puede hacer uso de la clase BackgroundWorker, que abstraerá este asunto para usted.

 30
Author: Frederik Gheysels,
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-09-16 10:05:49

Necesitará invocar el método en el subproceso de la GUI. Puedes hacerlo llamando a Control.Invocar.

Por ejemplo:

delegate void UpdateLabelDelegate (string message);

void UpdateLabel (string message)
{
    if (InvokeRequired)
    {
         Invoke (new UpdateLabelDelegate (UpdateLabel), message);
         return;
    }

    MyLabelControl.Text = message;
}
 26
Author: Kieron,
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
2009-03-19 09:47:27

Debido a la trivialidad del escenario, en realidad tendría la encuesta de subprocesos de la interfaz de usuario para el estado. Creo que encontrará 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 clasificación requerida cuando se utilizan los métodos ISynchronizeInvoke.Invoke y ISynchronizeInvoke.BeginInvoke. No hay nada malo con el uso de la técnica de clasificación, pero hay un par de advertencias que debe tener en cuenta.

  • Asegúrese de no llamar a BeginInvoke con demasiada frecuencia o podría invadir el mensaje bomba.
  • Llamar a Invoke en el hilo de trabajo es una llamada de bloqueo. Detendrá 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 interfaz de usuario los sondea. Este es un patrón común utilizado en muchos escenarios. Dado que todo lo que desea hacer es mostrar información de progreso del hilo de trabajo, entonces creo que encontrará que esta solución es una gran alternativa a la solución de marshaling. Tiene las siguientes ventajas.

  • La interfaz de usuario y los hilos de trabajo permanecen sueltos en comparación con el enfoque Control.Invoke o Control.BeginInvoke que los acopla estrechamente.
  • El subproceso de interfaz de usuario no impedirá el progreso del subproceso de trabajo.
  • El subproceso worker no puede dominar el tiempo que el subproceso UI pasa actualizando.
  • Los intervalos en los que la interfaz de usuario y los hilos de trabajo realizan operaciones pueden permanecer independiente.
  • El subproceso worker no puede sobrepasar la bomba de mensajes del subproceso UI.
  • El subproceso de interfaz de usuario dicta cuándo y con qué frecuencia se actualiza la interfaz de usuario.
 23
Author: Brian Gideon,
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-03-26 09:48:03

Ninguna de las cosas de invocación 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
}
 23
Author: Jon H,
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-03-26 10:06:14

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 GUI dentro del formulario (this) que puede cambiar tantos controles como desee. Llama a "updateGUI ()" desde el otro hilo. Los parámetros se pueden agregar a los valores de paso, o (probablemente más rápido) usar variables de ámbito de clase con bloqueos en ellos según sea necesario si hay alguna posibilidad de un choque entre los hilos que acceden a ellos que podría causar inestabilidad. Use BeginInvoke en lugar de Invoke si el hilo no-GUI es time critical (teniendo en cuenta la advertencia de Brian Gideon).

 19
Author: Frankg,
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-08-26 03:17:44

Esto en mi variación de 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 null-checking al resultado de la "as MemberExpression".
  2. Mejora la seguridad de tipo estático.

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

 19
Author: Rotaerk,
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-09-15 20:40:57

Esta es similar a la solución anterior usando.NET Framework 3.0, pero resolvió el problema de 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);
        }
    }
}

Para 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");
 19
Author: Francis,
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-03-26 09:56:51

Salvete! Habiendo 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 utilizando como una especie de visualización de registro. Necesitaba poder actualizar su texto de todos los hilos. Las líneas adicionales permiten que la ventana desplácese automáticamente a las líneas más recientes.

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

  form_Diagnostics.updateDiagWindow(whatmessage);

Código principal (pon esto dentro del código de clase de tu 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
 19
Author: bgmCoder,
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-03-26 09:58:47
Label lblText; //initialized elsewhere

void AssignLabel(string text)
{
   if (InvokeRequired)
   {
      BeginInvoke((Action<string>)AssignLabel, text);
      return;
   }

   lblText.Text = text;           
}

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

Al usar Invoke() está esperando que el método regrese. Ahora, puede ser que hagas algo en el código invocado que tendrá que esperar al hilo, lo que puede no ser inmediatamente obvio si está enterrado en algunas funciones que estás llamando, lo que a su vez puede suceder indirectamente a través de controladores de eventos. Así que lo haría estar esperando el hilo, el hilo estaría esperando por usted y usted está estancado.

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

 18
Author: ILoveFortran,
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-03-08 12:44:54

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

Haga 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(() => .....). Uso una función anónima o expresión lambda cuando trabajo en un hilo. Para reducir las líneas de código que puede usar el método ThreadStart(..) también que se supone que no debo explicar aquí.

 16
Author: ahmar,
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-04-24 07:23:27

Simplemente use algo como esto:

 this.Invoke((MethodInvoker)delegate
            {
                progressBar1.Value = e.ProgressPercentage; // runs on UI thread
            });
 14
Author: Hassan Shouman,
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-01-11 19:47:30

Puede utilizar el delegado ya existente Action:

private void UpdateMethod()
{
    if (InvokeRequired)
    {
        Invoke(new Action(UpdateMethod));
    }
}
 13
Author: Embedd_Khurja,
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-03-26 10:01:11

Intente 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);
    }
}
 12
Author: Ivaylo Slavov,
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-04-17 09:56:54

Mi versión es insertar una línea del "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.


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

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

 12
Author: MajesticRa,
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-03-26 10:00:26

Debe usar invoke y delegate

private delegate void MyLabelDelegate();
label1.Invoke( new MyLabelDelegate(){ label1.Text += 1; });
 11
Author: A. Zalonis,
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-09-10 13:55:34

Crear una variable de clase:

SynchronizationContext _context;

Establézcalo en el constructor que crea su IU:

var _context = SynchronizationContext.Current;

Cuando quiera actualizar la etiqueta:

_context.Send(status =>{
    // UPDATE LABEL
}, null);
 11
Author: blackmind,
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-03-26 10:10:52

La forma más fácil que creo:

   void Update()
   {
       BeginInvoke((Action)delegate()
       {
           //do your update
       });
   }
 6
Author: Vasily Semenov,
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-02-18 04:41:41

Por ejemplo, accede a un control que no esté en el hilo actual:

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

Allí el lblThreshold es una etiqueta y Speed_Threshold es una variable global.

 6
Author: Da Xiong,
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-03-26 10:11:42

Cuando estés en el subproceso de la interfaz de usuario, podrías pedirle su programador de tareas de contexto de sincronización. Te daría un TaskScheduler que programa todo en el subproceso de la interfaz de usuario.

Entonces puede encadenar sus tareas para que cuando el resultado esté listo, otra tarea (que está programada en el subproceso de IU) la elija y la 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 subprocesos) que son la forma preferida de escribir código concurrente ahora.

 6
Author: nosalan,
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-03-26 10:15:32

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 todo el pool.

¿Qué pasa si el identificador no se ha creado todavía?

La propiedad InvokeRequired , como se describe aquí (Control.InvokeRequired Property referencia a MSDN) devuelve true si la llamada se realizó desde un subproceso que no es el subproceso GUI, false si la llamada se realizó desde el subproceso GUI, o si el Controlador no se creó todavía.

Puedes encontrar una excepción si quieres que otro hilo muestre y actualice una forma modal. Debido a que desea que esa forma se muestre modalmente, 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 InvalidOperationExceptionsi las operaciones antes de la actualización de la etiqueta "toman menos tiempo" (leerlo e interpretarlo como una simplificación) que el tiempo que toma para el subproceso GUI para crear el Form's Handle. Esto sucede dentro del método ShowDialog().

También debe comprobar el Manejar así:

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 a realizar si el Handle aún no se ha creado: Simplemente puede ignorar la GUI actualizar (como se muestra en el código anterior) o puede esperar (más arriesgado). 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();
  }
}

Alimento mis formularios que se actualizan por otro subproceso con una instancia de este ThreadSafeGuiCommand , y defino métodos que actualizan la interfaz gráfica de usuario (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 tendré mi GUI actualizado cualquier hilo que haga la llamada, opcionalmente esperando una cantidad de tiempo bien definida (el tiempo de espera).

 6
Author: Sume,
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-03-26 10:20:01

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

He notado 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), Invoke lanza una excepción. Así que recomiendo siempre comprobar InvokeRequired antes de llamar Invoke o BeginInvoke.

 5
Author: Jos Bosmans,
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-02-06 09:46:30