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

从另一个线程更新Label的最简单方法是什么?

我在thread1上有一个Form ,然后从另一个线程( thread2 )开始。 虽然thread2正在处理一些文件,但我想用thread2的当前状态更新Form上的Label

我怎样才能做到这一点?


对于.NET 2.0,下面是我写的代码,它完全符合你的需求,适用于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 });
  }
}

像这样调用它:

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

如果您使用的是.NET 3.0或更高版本,则可以将上述方法重写为Control类的扩展方法,然后将该方法简化为:

myLabel.SetPropertyThreadSafe("Text", status);

UPDATE 05/10/2010:

对于.NET 3.0,您应该使用以下代码:

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

它使用LINQ和lambda表达式来允许更清晰,更简单和更安全的语法:

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

现在不仅在编译时检查属性名称,而且属性的类型也是如此,所以不可能(例如)将字符串值分配给布尔属性,从而导致运行时异常。

不幸的是,这并不能阻止任何人做愚蠢的事情,比如传递另一个Control的属性和价值,所以下面的代码会很愉快地编译:

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

因此,我添加了运行时检查,以确保传入的属性确实属于调用该方法的Control 。 不完美,但仍比.NET 2.0版本好很多。

如果任何人有进一步的建议,如何改进此代码的编译时安全性,请评论!


最简单的方法是传递给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

请注意, Invoke阻止执行直到它完成 - 这是同步代码。 这个问题没有询问关于异步代码的问题,但是当你想了解它时,Stack Overflow上有很多关于编写异步代码的内容。


处理长时间工作

从.NET 4.5和C#5.0开始,您应该在所有区域 (包括GUI)中使用基于任务的异步模式(TAP)以及异步 - 等待关键字:

TAP是推荐用于新开发的异步设计模式

而不是异步编程模型(APM)和基于事件的异步模式(EAP)(后者包括BackgroundWorker类)。

那么,为新开发推荐的解决方案是:

  • 事件处理程序的异步实现(是的,就是这样):

    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";
    }
    
  • 通知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());
            }
        }
    }
    
  • 注意以下几点:

  • 简洁且干净的代码以顺序方式编写,无需回调和显式线程。
  • 任务而不是线程。
  • async关键字,它允许使用await,从而阻止事件处理程序达到完成状态,直到任务完成并且同时不阻止UI线程。
  • 支持分离关注(SoC)设计原则的Progress类(参见IProgress接口),不需要显式调度器和调用。 它从创建位置(这里是UI线程)使用当前的SynchronizationContext。
  • TaskCreationOptions.LongRunning提示不要将任务排入ThreadPool。
  • 有关更详细的示例,请参阅:C#的未来:好消息来自Joseph Albahari“等待”的人。

    另请参阅有关UI线程模型的概念。

    处理异常

    下面的片段是如何处理异常并切换按钮的Enabled属性以防止后台执行过程中出现多次单击的示例。

    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/8825.html

    上一篇: How do I update the GUI from another thread?

    下一篇: Singleton: How should it be used