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

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

我怎么能这样做?


当前回答

由于场景的平凡性,我实际上会让UI线程轮询状态。我想你会发现它可以很优雅。

public class MyForm : Form
{
  private volatile string m_Text = "";
  private System.Timers.Timer m_Timer;

  private MyForm()
  {
    m_Timer = new System.Timers.Timer();
    m_Timer.SynchronizingObject = this;
    m_Timer.Interval = 1000;
    m_Timer.Elapsed += (s, a) => { MyProgressLabel.Text = m_Text; };
    m_Timer.Start();
    var thread = new Thread(WorkerThread);
    thread.Start();
  }

  private void WorkerThread()
  {
    while (...)
    {
      // Periodically publish progress information.
      m_Text = "Still working...";
    }
  }
}

该方法避免了使用ISynchronizeInvoke.Invoke和ISynchroniizeInvoke.BeginInvoke方法时所需的封送处理操作。使用封送处理技术没有什么问题,但有几点需要注意。

请确保不要过于频繁地调用BeginInvoke,否则它可能会超出消息泵。在工作线程上调用Invoke是一个阻塞调用。它将暂时停止该线程中正在执行的工作。

我在这个答案中提出的策略颠倒了线程的通信角色。UI线程轮询数据,而不是工作线程推送数据。这是许多场景中使用的常见模式。由于您所要做的只是显示工作线程的进度信息,所以我认为您会发现此解决方案是编组解决方案的一个很好的替代方案。它具有以下优点。

UI和工作线程保持松散耦合,而不是Control.Invoke或Control.BeginVoke方法紧密耦合它们。UI线程不会阻碍工作线程的进度。工作线程不能支配UI线程更新的时间。UI和工作线程执行操作的间隔可以保持独立。工作线程不能超出UI线程的消息泵。UI线程可以决定何时以及多久更新一次UI。

其他回答

由于场景的平凡性,我实际上会让UI线程轮询状态。我想你会发现它可以很优雅。

public class MyForm : Form
{
  private volatile string m_Text = "";
  private System.Timers.Timer m_Timer;

  private MyForm()
  {
    m_Timer = new System.Timers.Timer();
    m_Timer.SynchronizingObject = this;
    m_Timer.Interval = 1000;
    m_Timer.Elapsed += (s, a) => { MyProgressLabel.Text = m_Text; };
    m_Timer.Start();
    var thread = new Thread(WorkerThread);
    thread.Start();
  }

  private void WorkerThread()
  {
    while (...)
    {
      // Periodically publish progress information.
      m_Text = "Still working...";
    }
  }
}

该方法避免了使用ISynchronizeInvoke.Invoke和ISynchroniizeInvoke.BeginInvoke方法时所需的封送处理操作。使用封送处理技术没有什么问题,但有几点需要注意。

请确保不要过于频繁地调用BeginInvoke,否则它可能会超出消息泵。在工作线程上调用Invoke是一个阻塞调用。它将暂时停止该线程中正在执行的工作。

我在这个答案中提出的策略颠倒了线程的通信角色。UI线程轮询数据,而不是工作线程推送数据。这是许多场景中使用的常见模式。由于您所要做的只是显示工作线程的进度信息,所以我认为您会发现此解决方案是编组解决方案的一个很好的替代方案。它具有以下优点。

UI和工作线程保持松散耦合,而不是Control.Invoke或Control.BeginVoke方法紧密耦合它们。UI线程不会阻碍工作线程的进度。工作线程不能支配UI线程更新的时间。UI和工作线程执行操作的间隔可以保持独立。工作线程不能超出UI线程的消息泵。UI线程可以决定何时以及多久更新一次UI。

我无法理解这一丑陋实现背后的微软逻辑,但你必须有两个功能:

void setEnableLoginButton()
{
  if (InvokeRequired)
  {
    // btn_login can be any conroller, (label, button textbox ..etc.)

    btn_login.Invoke(new MethodInvoker(setEnable));

    // OR
    //Invoke(new MethodInvoker(setEnable));
  }
  else {
    setEnable();
  }
}

void setEnable()
{
  btn_login.Enabled = isLoginBtnEnabled;
}

这些片段对我有用,所以我可以在另一个线程上执行一些操作,然后更新GUI:

Task.Factory.StartNew(()=>
{
    // THIS IS NOT GUI
    Thread.Sleep(5000);
    // HERE IS INVOKING GUI
    btn_login.Invoke(new Action(() => DoSomethingOnGUI()));
});

private void DoSomethingOnGUI()
{
   // GUI
   MessageBox.Show("message", "title", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}

更简单的是:

btn_login.Invoke(new Action(()=>{ /* HERE YOU ARE ON GUI */ }));

您必须确保更新发生在正确的线程上;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)中,解决方案简单如下:

private void updateUI()
{
    if (!Dispatcher.CheckAccess())
    {
        Dispatcher.BeginInvoke(updateUI);
        return;
    }

    // Update any number of controls here
}

这是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”的结果中。它提高了静态类型的安全性。

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