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

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

我怎么能这样做?


当前回答

您必须确保更新发生在正确的线程上;UI线程。

为了做到这一点,您必须调用事件处理程序,而不是直接调用它。

您可以通过以下方式发起活动:

(代码是在我的脑海中打出来的,所以我没有检查语法等是否正确,但它应该能让你继续。)

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

请注意,上面的代码在WPF项目上不起作用,因为WPF控件不实现ISynchronizeInvoke接口。

为了确保上面的代码适用于Windows窗体和WPF以及所有其他平台,您可以查看AsyncOperation、AsyncOperationManager和SynchronizationContext类。

为了以这种方式轻松地引发事件,我创建了一个扩展方法,它允许我通过调用以下命令来简化引发事件:

MyEvent.Raise(this, EventArgs.Empty);

当然,您也可以使用BackGroundWorker类,它将为您抽象这个问题。

其他回答

为了在WPF中实现这一点,我采用以下方式。

 new Thread(() => 
 {
     while (...)
     {
         SomeLabel.Dispatcher.BeginInvoke((Action)(() => SomeLabel.Text = ...));
     }
 }).Start();

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

对于无参数:

    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}时,您只知道“从另一个线程调用此函数是安全的”。

Label lblText; //initialized elsewhere

void AssignLabel(string text)
{
   if (InvokeRequired)
   {
      BeginInvoke((Action<string>)AssignLabel, text);
      return;
   }

   lblText.Text = text;           
}

请注意,BeginInvoke()比Invoke(()更受欢迎,因为它不太可能导致死锁(然而,在这里,将文本分配给标签时,这不是问题):

使用Invoke()时,您正在等待方法返回。现在,可能是您在调用的代码中执行了一些需要等待线程的操作,如果它隐藏在您正在调用的某些函数中,这可能不会立即显现出来,而这本身可能会通过事件处理程序间接发生。因此,您将等待线程,线程将等待您,并且您处于死锁状态。

这实际上导致了我们发布的一些软件挂起。用BeginInvoke()替换Invoke(。除非需要同步操作(如果需要返回值,则可能是这种情况),否则请使用BeginInvoke()。

这是一个古老问题的新视角,使用了更实用的风格。如果您在所有项目中都保留TaskXM类,那么只有一行代码不再担心跨线程更新。

public class Example
{
    /// <summary>
    /// No more delegates, background workers, etc. Just one line of code as shown below.
    /// Note it is dependent on the Task Extension method shown next.
    /// </summary>
    public async void Method1()
    {
        // Still on the GUI thread here if the method was called from the GUI thread
        // This code below calls the extension method which spins up a new task and calls back.
        await TaskXM.RunCodeAsync(() =>
        {
            // Running an asynchronous task here
            // Cannot update the GUI thread here, but can do lots of work
        });
        // Can update GUI on this line
    }
}


/// <summary>
/// A class containing extension methods for the Task class
/// </summary>
public static class TaskXM
{
    /// <summary>
    /// RunCodeAsyc is an extension method that encapsulates the Task.run using a callback
    /// </summary>
    /// <param name="Code">The caller is called back on the new Task (on a different thread)</param>
    /// <returns></returns>
    public async static Task RunCodeAsync(Action Code)
    {
        await Task.Run(() =>
        {
            Code();
        });
        return;
    }
}

这是Ian Kemp解决方案的C#3.0变体:

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

你这样称呼它:

myButton.SetPropertyInGuiThread(b => b.Text, "Click Me!")

它将空检查添加到“as MemberExpression”的结果中。它提高了静态类型的安全性。

否则,原始版本是一个非常好的解决方案。