654 votos

Cómo actualizar la interfaz de usuario de otro hilo en C#?

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

Tengo un Form sobre thread1, desde que estoy empezando otro hilo (thread2). Mientras thread2 está en el procesamiento de algunos archivos me gustaría actualizar un Label sobre el Form con el estado actual de thread2's de trabajo.

¿Cómo puedo hacer eso?

628voto

Marc Gravell Puntos 482669

El más simple es un método anónimo:

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

451voto

Ian Kemp Puntos 6408

Para .NET 2.0, aquí tienes una buena bits de código que escribí que hace exactamente lo que desea, 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 });
  }
}

La llamada como esta:

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

Si usted está usando .NET 3.0 o superior, se puede reescribir el método anterior como un método de extensión de la Control de clase, que luego sería simplificar la llamada a:

myLabel.SetPropertyThreadSafe("Text", status);

ACTUALIZACIÓN 05/10/2010:

Para .NET 3.0 debe utilizar 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 ||
      !@this.GetType().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 las expresiones lambda para permitir mucho más limpia, más simple y más seguro sintaxis:

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

No sólo es el nombre de la propiedad que ahora se comprueban en tiempo de compilación, el tipo de la propiedad es así, por lo que es imposible (por ejemplo) asignar una cadena de valor para una propiedad booleana, y por lo tanto causar una excepción en tiempo de ejecución.

Por desgracia, esto no detiene a nadie de hacer cosas estúpidas como pasa en otro Control's de la propiedad y el valor, por lo que el siguiente estará feliz de compilar:

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

Por tanto, he añadido el tiempo de ejecución de comprobaciones para asegurarse de que el pasado-en los bienes no pertenecen realmente a la Control de que el método que se llama. No es perfecto, pero es mucho mejor que el .NET versión 2.0.

Si alguien tiene más sugerencias sobre cómo mejorar este código en tiempo de compilación seguridad, favor de comentar!

160voto

Ryszard Dżegan Puntos 4079

Desde .NET 4.5 y C# 5.0 debe utilizar Tarea-basado en modelo Asincrónico (TAP), junto con async-esperan palabras clave en todas las áreas (incluyendo la interfaz de usuario):

TAP es la recomendada asincrónica patrón de diseño para el desarrollo nuevo

en lugar del Modelo de Programación Asincrónica (APM) y basada en Eventos de modelo Asincrónico (EAP) (este último incluye la Clase BackgroundWorker).

Entonces, la solución recomendada para nuevo desarrollo es:

  1. Asincrónica implementación de un controlador de eventos (Sí, todos):

    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. La implementación de la segunda de hilo en la que se notifica el subproceso de 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());
            }
        }
    }
    

Observe los siguientes:

  1. Cortas y limpias código escrito en modo secuencial y sin incidencias y explícito de los hilos.
  2. Tarea en lugar de Hilo.
  3. async palabra clave, que permite usar esperan que a su vez evitar que el controlador de eventos de llegar a la finalización de estado hasta que finalizó la tarea y, mientras tanto, no bloquear el subproceso de interfaz de usuario.
  4. El progreso de la clase (ver IProgress Interfaz) que apoya la Separación de Preocupaciones (SoC) principio de diseño y no requiere explícito despachador y la invocación. Utiliza la actual SynchronizationContext de su lugar de creación (aquí el subproceso de interfaz de usuario).
  5. TaskCreationOptions.LongRunning que las sugerencias no de la cola de la tarea en ThreadPool.

Para un análisis más detallado de ejemplos ver: El Futuro de C#: cosas Buenas vienen a aquellos que esperan' por José Albahari.

Consulte también acerca de la interfaz de usuario de Modelo de Subprocesamiento concepto.

120voto

Zaid Masud Puntos 4536

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

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

O utilizar la Acción delegado en su lugar:

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

Ver aquí para una comparación de los dos: MethodInvoker vs Acción para Control.BeginInvoke

83voto

StyxRiver Puntos 1085

Dispara y olvida 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 puede ser llamado con la siguiente línea de código:

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

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X