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

1581

如何用最简单的方式从另一个线程更新Label

  • 我有一个在thread1上运行的Form,从那里我启动了另一个线程(thread2)。

  • thread2处理一些文件时,我想通过FormLabel的当前状态更新为thread2的工作状态。

我该怎么做呢?


28
.NET 2.0+有专门用于此的BackgroundWorker类,它可以注意UI线程。1.创建一个BackgroundWorker;2.添加两个委托(一个用于处理,另一个用于完成)。 - Preet Sangha
15
或许有点晚了:http://www.codeproject.com/KB/cs/Threadsafe_formupdating.aspx(请注意,为了保持翻译的准确性和简洁性,我省略了原文中的所有标点符号和格式) - MichaelD
4
请参考.NET 4.5和C# 5.0的答案:https://dev59.com/wnRB5IYBdhLWcg3wUVtd#18033198。 - Ryszard Dżegan
5
此问题不适用于Gtk# GUI。有关Gtk#的信息,请参见答案。 - hlovdal
3
警告:这个问题的回答现在混杂着 OT(“这是我为我的 WPF 应用所做的”)和历史遗留的 .NET 2.0 工件。请注意。 - Marc L.
相关帖子 - 从UI线程强制更新GUI - RBT
47个回答

18

只需要像这样使用:

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

如果你有 e.ProgressPercentage,那么你不是已经在调用此方法的 UI 线程中了吗? - LarsTech
ProgressChanged事件在UI线程上运行。这是使用BackgroundWorker的便利之一。Completed事件也在GUI上运行。唯一在非UI线程中运行的是DoWork方法。 - LarsTech

16

我的做法是插入一行递归“口诀”:

对于没有参数的情况:

    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 ()语句后面放置{}会影响代码的可读性,但在这种情况下,这是一个例行公事。如果这种方法在整个项目中都是一致的,它不会破坏代码的可读性。而且这样能够减少代码量(只需要一行代码,而不是五行)。

当你看到if(InvokeRequired) {something long}时,你知道“这个函数可以安全地从另一个线程中调用”。


16

您可以使用已经存在的委托 Action:

private void UpdateMethod()
{
    if (InvokeRequired)
    {
        Invoke(new Action(UpdateMethod));
    }
}

15

又是一个通用的Control扩展方法..

首先,为类型为Control的对象添加一个扩展方法。

public static void InvokeIfRequired<T>(this T c, Action<T> action) where T : Control
{
    if (c.InvokeRequired)
    {
        c.Invoke(new Action(() => action(c)));
    }
    else
    {
        action(c);
    }
}

在另一个线程中这样调用,以访问名为object1的UI线程控件:

object1.InvokeIfRequired(c => { c.Visible = true; });
object1.InvokeIfRequired(c => { c.Text = "ABC"; });

..or like this

object1.InvokeIfRequired(c => 
  { 
      c.Text = "ABC";
      c.Visible = true; 
  }
);

非常优雅,非常好! - Mecanik
我已经开始使用c.BeginInvoke进行异步更新。如果在级联中调用,它不太可能导致死锁。 - flodis

15
创建一个类变量:
SynchronizationContext _context;

在创建用户界面的构造函数中设置它:

var _context = SynchronizationContext.Current;

当您想要更新标签时:

_context.Send(status =>{
    // UPDATE LABEL
}, null);

13

尝试使用此方法刷新标签

public static class ExtensionMethods
{
    private static Action EmptyDelegate = delegate() { };

    public static void Refresh(this UIElement uiElement)
    {
        uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
    }
}

这是针对 Windows Forms 的吗? - Kiquenet

13

你必须使用invoke和delegate。

private delegate void MyLabelDelegate();
label1.Invoke( new MyLabelDelegate(){ label1.Text += 1; });

12

在WPF应用程序中最简单的方法是:

this.Dispatcher.Invoke((Action)(() =>
{
    // This refers to a form in a WPF application 
    val1 = textBox.Text; // Access the UI 
}));

4
如果你正在使用WPF应用程序,那么这是正确的。但他正在使用Windows Forms。 - Gertjan Gielen
即使在Winforms应用程序中,您也可以使用Dispatcher。 - Francis

10

当您在UI线程中时,可以请求其同步上下文任务调度程序。它会给您提供一个TaskScheduler,该调度程序将所有任务都安排在UI线程上。

然后,您可以链式编写您的任务,这样当结果准备好后,另一个任务(在UI线程上安排)会接收它并将其分配给一个标签。

public partial class MyForm : Form
{
  private readonly TaskScheduler _uiTaskScheduler;
  public MyForm()
  {
    InitializeComponent();
    _uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
  }

  private void buttonRunAsyncOperation_Click(object sender, EventArgs e)
  {
    RunAsyncOperation();
  }

  private void RunAsyncOperation()
  {
    var task = new Task<string>(LengthyComputation);
    task.ContinueWith(antecedent =>
                         UpdateResultLabel(antecedent.Result), _uiTaskScheduler);
    task.Start();
  }

  private string LengthyComputation()
  {
    Thread.Sleep(3000);
    return "47";
  }

  private void UpdateResultLabel(string text)
  {
    labelResult.Text = text;
  }
}

这适用于任务(而不是线程),它们现在是编写并发代码的首选方式


1
调用 Task.Start 通常不是一个好的实践 http://blogs.msdn.com/b/pfxteam/archive/2012/01/14/10256832.aspx - Ohad Schneider

9
即使该操作很耗时(例如在我的示例中使用的thread.sleep),此代码也不会锁定您的用户界面:
 private void button1_Click(object sender, EventArgs e)
 {

      Thread t = new Thread(new ThreadStart(ThreadJob));
      t.IsBackground = true;
      t.Start();         
 }

 private void ThreadJob()
 {
     string newValue= "Hi";
     Thread.Sleep(2000); 

     this.Invoke((MethodInvoker)delegate
     {
         label1.Text = newValue; 
     });
 }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接