How do I update the GUI from another thread?

What is the simplest way to update a Label from another thread?

I have a Form on thread1 , and from that I'm starting another thread ( thread2 ). While thread2 is processing some files I would like to update a Label on the Form with the current status of thread2 's work.

How can I do that?


For .NET 2.0, here's a nice bit of code I wrote that does exactly what you want, and works for any property on a 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 });
  }
}

Call it like this:

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

If you're using .NET 3.0 or above, you could rewrite the above method as an extension method of the Control class, which would then simplify the call to:

myLabel.SetPropertyThreadSafe("Text", status);

UPDATE 05/10/2010:

For .NET 3.0 you should use this code:

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

which uses LINQ and lambda expressions to allow much cleaner, simpler and safer syntax:

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

Not only is the property name now checked at compile time, the property's type is as well, so it's impossible to (for example) assign a string value to a boolean property, and hence cause a runtime exception.

Unfortunately this doesn't stop anyone from doing stupid things such as passing in another Control 's property and value, so the following will happily compile:

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

Hence I added the runtime checks to ensure that the passed-in property does actually belong to the Control that the method's being called on. Not perfect, but still a lot better than the .NET 2.0 version.

If anyone has any further suggestions on how to improve this code for compile-time safety, please comment!


The simplest way is an anonymous method passed into 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

Notice that Invoke blocks execution until it completes--this is synchronous code. The question doesn't ask about asynchronous code, but there is lots of content on Stack Overflow about writing asynchronous code when you want to learn about it.


Handling long work

Since .NET 4.5 and C# 5.0 you should use Task-based Asynchronous Pattern (TAP) along with async - await keywords in all areas (including the GUI):

TAP is the recommended asynchronous design pattern for new development

instead of Asynchronous Programming Model (APM) and Event-based Asynchronous Pattern (EAP) (the latter includes the BackgroundWorker Class).

Then, the recommended solution for new development is:

  • Asynchronous implementation of an event handler (Yes, that's all):

    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";
    }
    
  • Implementation of the second thread that notifies the UI thread:

    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());
            }
        }
    }
    
  • Notice the following:

  • Short and clean code written in sequential manner without callbacks and explicit threads.
  • Task instead of Thread.
  • async keyword, that allows to use await which in turn prevent the event handler from reaching the completion state till the task finished and in the meantime doesn't block the UI thread.
  • Progress class (see IProgress Interface) that supports Separation of Concerns (SoC) design principle and doesn't require explicit dispatcher and invoking. It uses the current SynchronizationContext from its creation place (here the UI thread).
  • TaskCreationOptions.LongRunning that hints to do not queue the task into ThreadPool.
  • For a more verbose examples see: The Future of C#: Good things come to those who 'await' by Joseph Albahari.

    See also about UI Threading Model concept.

    Handling exceptions

    The below snippet is an example of how to handle exceptions and toggle button's Enabled property to prevent multiple clicks during background execution.

    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...");
        }
    }
    
    链接地址: http://www.djcxy.com/p/8826.html

    上一篇: 抽象工厂模式与工厂方法的区别

    下一篇: 我如何从另一个线程更新GUI?