首页 > C# > 如何从另一个线程更新 GUI?

如何从另一个线程更新 GUI?

上一篇 下一篇

Which is the simplest way to update a Label from another Thread?

  • I have a Form running 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 could I do that?

网友回答:

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.

网友回答:

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:

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

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!

网友回答:

Handling long work

Since .NET 4.5 and C# 5.0 you should use Task-based Asynchronous Pattern (TAP) along with asyncawait 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:

  1. 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";
     }
    
  2. 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:

  1. Short and clean code written in sequential manner without callbacks and explicit threads.
  2. Task instead of Thread.
  3. 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.
  4. 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).
  5. 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...");
    }
}

模板简介:该模板名称为【如何从另一个线程更新 GUI?】,大小是暂无信息,文档格式为.编程语言,推荐使用Sublime/Dreamweaver/HBuilder打开,作品中的图片,文字等数据均可修改,图片请在作品中选中图片替换即可,文字修改直接点击文字修改即可,您也可以新增或修改作品中的内容,该模板来自用户分享,如有侵权行为请联系网站客服处理。欢迎来懒人模板【C#】栏目查找您需要的精美模板。

相关搜索
  • 下载密码 lanrenmb
  • 下载次数 252次
  • 使用软件 Sublime/Dreamweaver/HBuilder
  • 文件格式 编程语言
  • 文件大小 暂无信息
  • 上传时间 02-10
  • 作者 网友投稿
  • 肖像权 人物画像及字体仅供参考
栏目分类 更多 >
热门推荐 更多 >
微信图片 html5 微信素材 微信文章 企业网站 单页式简历模板 微信公众平台 自适应 响应式 微信模板
您可能会喜欢的其他模板