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

我在thread1上运行了一个Form,然后我开始另一个线程(thread2)。当thread2处理一些文件时,我想用thread2工作的当前状态更新表单上的标签。

我怎么能这样做?


当前回答

例如,访问当前线程以外的控件:

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

lblThreshold是一个标签,Speed_Threshold则是一个全局变量。

其他回答

对于.NET 2.0,下面是我编写的一段代码,它完全符合您的需要,适用于控件上的任何属性:

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

2010年10月5日更新:

对于.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表达式来允许更干净、更简单和更安全的语法:

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

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

不幸的是,这并不能阻止任何人做一些愚蠢的事情,例如传递另一个控件的属性和值,因此以下内容将很好地编译:

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

因此,我添加了运行时检查,以确保传入的属性实际上属于调用该方法的控件。虽然不完美,但仍比.NET2.0版本好得多。

如果有人对如何改进此代码以确保编译时安全有任何进一步的建议,请发表评论!

我的版本是插入一行递归的“咒语”:

对于无参数:

    void Aaaaaaa()
    {
        if (InvokeRequired) { Invoke(new Action(Aaaaaaa)); return; } //1 line of mantra

        // Your code!
    }

对于具有参数的函数:

    void Bbb(int x, string text)
    {
        if (InvokeRequired) { Invoke(new Action<int, string>(Bbb), new[] { x, text }); return; }
        // Your code!
    }

就是这样。


一些论证:通常在if()语句后一行放置{}对代码可读性不利。但在这种情况下,这是例行的“咒语”。如果此方法在项目中保持一致,则不会破坏代码的可读性。它还可以避免代码乱丢(一行代码而不是五行代码)。

当您看到(InvokeRequired){some long}时,您只知道“从另一个线程调用此函数是安全的”。

只需使用以下内容:

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

在这个问题上,大多数其他答案对我来说都有点复杂(我是C#新手),所以我写了我的答案:

我有一个WPF应用程序,并定义了如下工作人员:

问题:

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

解决方案:

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

我还没有弄清楚上面这句话的意思,但它奏效了。

对于WinForms:

解决方案:

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

前面回答中的Invoke内容都不是必需的。

您需要查看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
}