¿Cómo actualizo la GUI desde otro hilo?

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

  • Tengo un Form que se ejecuta en thread1, y a partir de eso estoy comenzando otro hilo (thread2).

  • Aunque la thread2 está procesando algunos archivos, me gustaría actualizar un Label al Form con el estado actual de thread2El trabajo de

¿Cómo puedo hacer eso?

preguntado el 19 de marzo de 09 a las 07:03

¿No tiene .net 2.0+ la clase BackgroundWorker solo para esto? Es compatible con el hilo de la interfaz de usuario. 1. Cree un BackgroundWorker 2. Agregue dos delegados (uno para procesar y otro para completar) -

Vea la respuesta para .NET 4.5 y C # 5.0: stackoverflow.com/a/18033198/2042090 -

Esta pregunta no se aplica a Gtk # GUI. Para Gtk # ver este y este respuesta. -

Cuidado: las respuestas a esta pregunta ahora son un desorden desordenado de OT ("esto es lo que hice para mi aplicación WPF") y artefactos históricos de .NET 2.0. -

30 Respuestas

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, puede reescribir el método anterior como un método de extensión del Control class, que luego simplificaría la llamada a:

myLabel.SetPropertyThreadSafe("Text", status);

ACTUALIZACIÓN 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 ||
      !@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 usa expresiones LINQ y lambda 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 ahora el nombre de la propiedad en el momento de la compilación, sino que también se comprueba 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, provocar una excepción en tiempo de ejecución.

Desafortunadamente, esto no impide que nadie haga cosas estúpidas, como pasar a otro Control's propiedad y valor, por lo que se compilará felizmente lo siguiente:

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

Por lo tanto, agregué las verificaciones de tiempo de ejecución para asegurarme de que la propiedad pasada realmente pertenezca al Control que se está utilizando el método. No es perfecto, pero 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, ¡comente!

respondido 03 mar '15, 00:03

Hay casos en los que this.GetType () se evalúa igual que propertyInfo.ReflectedType (por ejemplo, LinkLabel en WinForms). No tengo una gran experiencia en C #, pero creo que la condición para la excepción debería ser: if (propertyInfo == null || (!@this.GetType (). IsSubclassOf (propertyInfo.ReflectedType) && @ this.GetType ( )! = propertyInfo.ReflectedType) || @ this.GetType (). GetProperty (propertyInfo.Name, propertyInfo.PropertyType) == null) - Corvin

@lan puede esto SetControlPropertyThreadSafe(myLabel, "Text", status) ser llamado desde otro módulo, clase o formulario - Smith

La solución proporcionada es innecesariamente compleja. Vea la solución de Marc Gravell, o la solución de Zaid Masud, si valora la simplicidad. - franco hileman

Esta solución desperdicia toneladas de recursos si actualiza varias propiedades, ya que cada invocación cuesta una gran cantidad de recursos. De todos modos, no creo que sea así como se pretendía la función de seguridad de subprocesos. Encapsulte las acciones de actualización de la interfaz de usuario e invoquelas UNA VEZ (y no por propiedad) - Consola

¿Por qué diablos usarías este código sobre el componente BackgroundWorker? - Andy

El más simple 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

Darse cuenta de Invoke bloquea la ejecución hasta que se completa; se trata de código síncrono. La pregunta no se refiere al código asincrónico, pero hay muchas contenido en Stack Overflow sobre cómo escribir código asincrónico cuando quiera aprender sobre él.

Respondido el 16 de Septiembre de 17 a las 10:09

Pero, ¿entonces su función de procesamiento debe ser un método miembro de su formulario GUI? - Federico Gheysels

Viendo que el OP no ha mencionado ninguna clase / instancia excepto el formulario, que no es un mal predeterminado ... - Marc Gravell ♦

No olvide que la palabra clave "this" hace referencia a una clase "Control". - ARIZONA.

@codecompletarlo es seguro de cualquier manera, y ya sabemos que estamos en un trabajador, entonces, ¿por qué verificar algo que sabemos? - Marc Gravell ♦

@Dragouf no realmente: uno de los puntos de usar este método es que ya sabe qué partes se ejecutan en el trabajador y cuáles se ejecutan en el hilo de la interfaz de usuario. No es necesario comprobarlo. - Marc Gravell ♦

Manejo de trabajos largos

Como .NET 4.5 y C # 5.0 Deberías usar Patrón asincrónico basado en tareas (TAP) para cada año fiscal junto con la Asincrónico-esperar las palabras claves en todas las áreas (incluida la GUI):

TAP es el patrón de diseño asincrónico recomendado para nuevos desarrollos

en lugar de Modelo de programación asincrónica (APM) y Patrón asincrónico basado en eventos (EAP) (este último incluye el Clase BackgroundWorker).

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

  1. Implementación asincrónica 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 al hilo de la IU:

    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 ni subprocesos explícitos.
  2. Tarea en lugar de Hilo.
  3. Asincrónico palabra clave, que permite usar esperar lo que a su vez evita que el controlador de eventos alcance el estado de finalización hasta que finalice la tarea y, mientras tanto, no bloquea el hilo de la interfaz de usuario.
  4. Clase de progreso (ver Interfaz de IProgress) que apoya Separación de preocupaciones (SoC) principio de diseño y no requiere un despachador explícito ni una invocación. Utiliza la corriente SynchronizationContextContexto de sincronización desde su lugar de creación (aquí el hilo de la interfaz de usuario).
  5. TaskCreationOptions.LongRunning que sugiere no poner la tarea en cola en Grupo de subprocesos.

Para obtener ejemplos más detallados, consulte: El futuro de C #: las cosas buenas les llegan a los que 'esperan' by José Albahari.

Ver también sobre Modelo de subprocesos de interfaz de usuario concepto.

Manejo de excepciones

El siguiente fragmento es un ejemplo de cómo manejar excepciones y alternar botones Enabled propiedad 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...");
    }
}

contestado el 23 de mayo de 17 a las 12:05

If SecondThreadConcern.LongWork() lanza una excepción, ¿puede ser detectada por el hilo de la interfaz de usuario? Esta es una publicación excelente, por cierto. - kdbanman

He agregado una sección adicional a la respuesta para cumplir con sus requisitos. Saludos. - Ryszard Dżegan

El ExceptionDispatchInfo clase es responsable de ese milagro de volver a lanzar la excepción en segundo plano en el hilo de la interfaz de usuario en el patrón async-await. - Ryszard Dżegan

¿Soy solo yo al pensar que esta forma de hacer esto es mucho más detallada que simplemente invocar Invoke / Begin? - MeTito

Task.Delay(500).Wait()? ¿Cuál es el punto de crear una tarea para bloquear el hilo actual? ¡Nunca debe bloquear un subproceso de grupo de subprocesos! - Yarik

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

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

O use Action delegate en su lugar:

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

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

contestado el 23 de mayo de 17 a las 13:05

¿Qué es "control" en este ejemplo? ¿Mi control de IU? Intento implementar esto en WPF en un control de etiqueta, e Invoke no es miembro de mi etiqueta. - Dbloom

De que se trata método de extensión como @styxriver stackoverflow.com/a/3588137/206730 ? - Kiquenet

declare 'Acción y;' dentro de la clase o método cambiar la propiedad del texto y actualizar el texto con este fragmento de código 'yourcontrol.Invoke (y = () => yourcontrol.Text = "new text");' - antonio leite

@Dbloom no es miembro porque es solo para WinForms. Para WPF, usa Dispatcher.Invoke - lento

Estaba siguiendo esta solución, pero a veces mi interfaz de usuario no se actualizaba. Encontré que necesito this.refresh() forzar invalidar y volver a pintar la GUI .. si es útil .. - Rakibul Haq

Activar y olvidar 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");

Respondido 27 ago 10, 22:08

¿Cuál es el sentido del uso de @this? ¿No sería equivalente "control"? ¿Hay algún beneficio para @this? - argyle

@jeromeyers - El @this es simplemente el nombre de la variable, en este caso la referencia al control actual que llama a la extensión. Puede cambiarle el nombre a fuente, o lo que sea que flote en su barco. yo suelo @this, porque se refiere a 'este Control' que llama a la extensión y es consistente (en mi cabeza, al menos) con el uso de la palabra clave 'this' en el código normal (sin extensión). - Río Styx

Esto es genial, fácil y para mí es la mejor solución. Puede incluir todo el trabajo que tiene que hacer en el hilo de la interfaz de usuario. Ejemplo: this.UIThread (() => {txtMessage.Text = message; listBox1.Items.Add (message);}); - Automotiz

Me gusta mucho esta solución. Liendre menor: nombraría este método OnUIThread más bien que UIThread. - Fabricante de herramientasSteve

Por eso llamé a esta extensión RunOnUiThread. Pero eso es solo gusto personal. - grisgrama

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 trabajador tiene un evento. Su subproceso de la interfaz de usuario comienza con otro subproceso para hacer el trabajo y conecta ese evento de trabajador para que pueda 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.

respondido 26 mar '17, 10:03

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
}

Respondido el 19 de diciembre de 15 a las 10:12

¡bien hecho por la sencillez! no solo es simple, sino que también funciona bien. ¡Realmente no entendía por qué Microsoft no podía hacerlo más simple como debe ser! para llamar a 1 línea en el hilo principal, ¡deberíamos escribir un par de funciones! - MBH

@MBH De acuerdo. Por cierto, ¿te diste cuenta? stackoverflow.com/a/3588137/199364 respuesta anterior, que define un método de extensión? Haga eso una vez en una clase de utilidades personalizadas, entonces no tendrá que preocuparse más de que Microsoft no lo haya hecho por nosotros :) - Fabricante de herramientasSteve

@ToolmakerSteve ¡Eso es exactamente lo que significaba ser! tienes razón, podemos encontrar una manera, pero me refiero desde el punto de vista de DRY (no te repitas), el problema que tiene una solución común, puede ser resuelto por ellos con un mínimo esfuerzo por parte de Microsoft, lo que ahorrará mucho tiempo para programadores :) - MBH

El código de subprocesamiento a menudo tiene errores y siempre es difícil de probar. No es necesario escribir código de subprocesos para actualizar la interfaz de usuario desde una tarea en segundo plano. Solo usa el FondoTrabajador clase para ejecutar la tarea y su Informe de progreso método para actualizar la interfaz de usuario. Por lo general, solo informa un porcentaje completo, 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 desea actualizar el mismo campo. Si tiene que realizar 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 configurar el WorkerReportsProgress bandera, o la ReportProgress El método será completamente ignorado.

Respondido el 22 de Septiembre de 12 a las 04:09

Al final del procesamiento, también es posible actualizar la interfaz de usuario a través de backgroundWorker1_RunWorkerCompleted. - DavidRR

La gran mayoría de las respuestas usan Control.Invoke el cual es un condición de carrera a la espera de 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 this.Invoke se llama (recuerda, this es la Form objeto), un ObjectDisposedException probablemente será despedido.

La solución es usar SynchronizationContext, específicamente SynchronizationContext.Current as hamilton.danieb sugiere (otras respuestas se basan en SynchronizationContext implementaciones que es completamente innecesario). Modificaría ligeramente su código para usar SynchronizationContext.Post más bien que SynchronizationContext.Send aunque (ya que normalmente no es necesario 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 versiones posteriores debería utilizar tareas para operaciones asíncronas. Ver n-san's respuesta para el enfoque equivalente basado en tareas (utilizando TaskScheduler.FromCurrentSynchronizationContext).

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

contestado el 23 de mayo de 17 a las 13:05

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

Para hacer esto, deberá invocar el controlador de eventos en lugar de llamarlo directamente.

Puede hacer esto elevando su evento de esta manera:

(El código está escrito aquí fuera de mi cabeza, por lo que no he verificado la sintaxis correcta, etc., pero debería ayudarlo).

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 proyectos de WPF, ya que los controles de WPF no implementan la ISynchronizeInvoke de la interfaz del.

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

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

MyEvent.Raise(this, EventArgs.Empty);

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

Respondido el 16 de Septiembre de 17 a las 11:09

De hecho, pero no me gusta "saturar" mi código GUI con este asunto. A mi GUI no debería importarle si necesita invocar o no. En otras palabras: no creo que sea responsabilidad de la GUI realizar el cambio de contexto. - Federico Gheysels

Separar al delegado, etc. parece una exageración, ¿por qué no solo: SynchronizationContext.Current.Send (delegate {MyEvent (...);}, null); - Marc Gravell ♦

¿Siempre tiene acceso al SynchronizationContext? ¿Incluso si su clase está en una biblioteca de clases? - Federico Gheysels

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 utiliza el ISynchronizeInvoke.Invoke y ISynchronizeInvoke.BeginInvoke métodos. No hay nada de malo en usar la técnica de clasificación, pero hay un par de advertencias que debe tener en cuenta.

  • Asegúrate de no llamar BeginInvoke con demasiada frecuencia o podría invadir la bomba de mensajes.
  • llamar Invoke en el hilo de trabajo hay una llamada de bloqueo. Detendrá temporalmente el trabajo que se está realizando 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 envíe los datos, el subproceso de la 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 la información de progreso del hilo de trabajo, creo que encontrará que esta solución es una excelente alternativa a la solución de clasificación. Tiene las siguientes ventajas.

  • La interfaz de usuario y los subprocesos de trabajo permanecen débilmente acoplados a diferencia de los Control.Invoke or Control.BeginInvoke acercamiento que los une fuertemente.
  • El subproceso de la 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 IU y los subprocesos de trabajo realizan operaciones pueden permanecer independientes.
  • El subproceso de trabajo no puede invadir el bombeo de mensajes del subproceso de la interfaz de usuario.
  • El hilo de la interfaz de usuario determina cuándo y con qué frecuencia se actualiza la interfaz de usuario.

respondido 26 mar '17, 10:03

Buena idea. Lo único que no mencionó es cómo desecha correctamente el temporizador una vez que WorkerThread ha terminado. Tenga en cuenta que esto puede causar problemas cuando finaliza la aplicación (es decir, el usuario cierra la aplicación). ¿Tienes una idea de cómo solucionar esto? - Matt

@Matt En lugar de utilizar un controlador anónimo para Elapsed evento, usa un método de miembro para que pueda eliminar el temporizador cuando se elimina el formulario ... - Phil1970

@ Phil1970 - Buen punto. Quisiste decir como System.Timers.ElapsedEventHandler handler = (s, a) => { MyProgressLabel.Text = m_Text; }; y asignándolo a través de m_Timer.Elapsed += handler;, más adelante en el contexto de disposición haciendo un m_Timer.Elapsed -= handler; estoy en lo cierto? Y para la disposición / cierre siguiendo los consejos comentados aquí. - Matt

Deberá invocar el método en el hilo de la GUI. Puede 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;
}

respondido 19 mar '09, 09:03

La línea de invocación me da un error de compilación. La mejor coincidencia de método sobrecargado para 'System.Windows.Forms.Control.Invoke (System.Delegate, object [])' tiene algunos argumentos no válidos: CruelIO

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

Necesita 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
}

respondido 26 mar '17, 11:03

¿Qué crees que usa el método Post bajo el capó? :) - incredulidad

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");

respondido 26 mar '17, 10:03

¡Salvete! Habiendo buscado esta pregunta, encontré las respuestas por FrankG y Oregon fantasma ser lo más fácil y útil 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 desde 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 manera en que cree que funcionaría sin ningún subproceso:

  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

respondido 26 mar '17, 10:03

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 sobre Invoke() porque es menos probable que cause interbloqueos (sin embargo, esto no es un problema aquí cuando solo se asigna texto a una etiqueta):

Cuando usas Invoke() está esperando que vuelva el método. Ahora, puede ser que haga algo en el código invocado que deba esperar el hilo, lo que puede no ser inmediatamente obvio si está enterrado en algunas funciones que está llamando, lo que a su vez puede suceder indirectamente a través de controladores de eventos. Entonces estarías esperando el hilo, el hilo estaría esperando por ti y estás en un punto muerto.

En realidad, esto provocó que parte de nuestro software publicado se bloqueara. 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().

respondido 08 mar '17, 12:03

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 (this) 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 en ellos según sea necesario si existe alguna posibilidad de un conflicto entre los subprocesos que acceden a ellos que podría causar inestabilidad. Use BeginInvoke en lugar de Invoke si el hilo que no es de la GUI es crítico en el tiempo (teniendo en cuenta la advertencia de Brian Gideon).

Respondido 26 ago 10, 04:08

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. Agrega comprobación de nulos al resultado de "as MemberExpression".
  2. Mejora la seguridad de tipo estático.

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

Respondido el 15 de Septiembre de 11 a las 21:09

Cuando encontré el mismo problema, busqué ayuda en Google, pero en lugar de darme una solución simple, me confundió más al dar ejemplos de MethodInvoker y bla, bla, bla. Entonces 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 hilo nuevo como este

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

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

Respondido 24 Abr '17, 08:04

Simplemente use algo como esto:

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

Respondido el 11 de enero de 16 a las 19:01

Si no e.ProgressPercentage, ¿no estás ya en el hilo de la interfaz de usuario del método al que llamas? - LarsTech

El evento ProgressChanged se ejecuta en el subproceso de la interfaz de usuario. Esa es una de las ventajas de utilizar BackgroundWorker. El evento Completado también se ejecuta en la interfaz gráfica de usuario. Lo único que se ejecuta en el subproceso que no es de interfaz de usuario es el método DoWork. - LarsTech

Mi versión es para 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.


Alguna argumentación: Por lo general, es malo para la legibilidad del código colocar {} después de un if () declaración en una línea. Pero en este caso se trata de un "mantra" de rutina. No rompe la legibilidad del código si este método es coherente en el proyecto. Y evita que su código se ensucie (una línea de código en lugar de cinco).

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

respondido 26 mar '17, 11:03

Puede utilizar el delegado ya existente Action:

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

respondido 26 mar '17, 11:03

La mayoría de las otras respuestas son un poco complejas para mí en esta pregunta (soy nuevo en C #), así que estoy escribiendo la mía:

Tengo un WPF aplicación y han definido un trabajador de la siguiente manera:

Edición:

BackgroundWorker workerAllocator;
workerAllocator.DoWork += delegate (object sender1, DoWorkEventArgs e1) {
    // This is my DoWork function.
    // It is given as an anonymous function, instead of a separate DoWork function

    // I need to update a message to textbox (txtLog) from this thread function

    // Want to write below line, to update UI
    txt.Text = "my message"

    // But it fails with:
    //  'System.InvalidOperationException':
    //  "The calling thread cannot access this object because a different thread owns it"
}

Solución:

workerAllocator.DoWork += delegate (object sender1, DoWorkEventArgs e1)
{
    // The below single line works
    txtLog.Dispatcher.BeginInvoke((Action)(() => txtLog.Text = "my message"));
}

Todavía tengo que averiguar qué significa la línea anterior, pero funciona.

Para WinForms:

Solución:

txtLog.Invoke((MethodInvoker)delegate
{
    txtLog.Text = "my message";
});

Respondido 11 Jul 18, 05:07

La pregunta era sobre Winforms, no sobre WPF. - Marc L.

Gracias. Se agregó la solución WinForms arriba. - Manohar Reddy Poreddy

... que es solo una copia de muchas otras respuestas a esta misma pregunta, pero está bien. ¿Por qué no ser parte de la solución y simplemente eliminar su respuesta? - Marc L.

hmm, correcto, lo eres, si tan solo leyeras mi respuesta con atención, la parte inicial (la razón por la que escribí la respuesta), y con suerte, con un poco más de atención, ves que hay alguien que tuvo exactamente el mismo problema y votó a favor hoy por mi respuesta simple, y con aún más atención si pudiera prever la historia real de por qué sucedió todo esto, que google me envía aquí incluso cuando busco wpf. Claro, ya que se perdió estas 3 razones más o menos obvias, puedo entender por qué no eliminará su voto negativo. En lugar de limpiar el que está bien, cree algo nuevo que sea mucho más difícil. - Manohar Reddy Poreddy

Crea una variable de clase:

SynchronizationContext _context;

Configúrelo en el constructor que crea su interfaz de usuario:

var _context = SynchronizationContext.Current;

Cuando desee actualizar la etiqueta:

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

respondido 26 mar '17, 11:03

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

Respondido 17 Abr '14, 10:04

Es para Formularios de Windows ? - Kiquenet

Debes usar invocar y delegar

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

Respondido el 10 de Septiembre de 13 a las 14:09

Por ejemplo, acceda a un control que no sea el del hilo actual:

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

He aquí el lblThreshold es una etiqueta y Speed_Threshold es una variable global.

respondido 26 mar '17, 11:03

Cuando esté en el hilo de la interfaz de usuario, puede pedirle su programador de tareas de contexto de sincronización. Te daría un Programador de tareas 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 hilo 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 subprocesos) que son los forma preferida de escribir código concurrente ahora.

respondido 26 mar '17, 11:03

llamar Task.Start normalmente no es una buena práctica blogs.msdn.com/b/pfxteam/archive/2012/01/14/10256832.aspx - Ohad Schneider

La forma más sencilla en las aplicaciones WPF es:

this.Dispatcher.Invoke((Action)(() =>
{
    // This refers to a form in a WPF application 
    val1 = textBox.Text; // Access the UI 
}));

respondido 26 mar '17, 11:03

Esto es correcto, si está utilizando una aplicación WPF. Pero está usando Windows Forms. - Gertjan Gielen

Puede utilizar Dispatcher incluso en una aplicación Winforms. stackoverflow.com/questions/303116/… - Francis

La forma más fácil en la que pienso:

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

Respondido 18 Feb 14, 04:02

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.