如何从另一个线程更新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个回答

5

首先获取您的表单实例(在本例中为 mainForm),然后在另一个线程中使用以下代码。

mainForm.Invoke(new MethodInvoker(delegate () 
{
    // Update things in my mainForm here
    mainForm.UpdateView();
}));

4

将一些常用变量放在一个单独的类中来保存它们的值。

例如:

public  class data_holder_for_controls
{
    // It will hold the value for your label
    public string status = string.Empty;
}

class Demo
{
    public static  data_holder_for_controls d1 = new data_holder_for_controls();

    static void Main(string[] args)
    {
        ThreadStart ts = new ThreadStart(perform_logic);
        Thread t1 = new Thread(ts);
        t1.Start();
        t1.Join();
        //your_label.Text=d1.status; --- can access it from any thread
    }

    public static void perform_logic()
    {
        // Put some code here in this function
        for (int i = 0; i < 10; i++)
        {
            // Statements here
        }
        // Set the result in the status variable
        d1.status = "Task done";
    }
}

3

一般的做法如下:

using System;
using System.Threading;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        int clickCount = 0;

        public Form1()
        {
            InitializeComponent();
            label1.SetText("0");
        }

        private void button1_Click(object sender, EventArgs e)
        {
            new Thread(() => label1.SetText((++clickCount).ToString())).Start();
        }
    }

    public static class ControlExtensions
    {
        public static void SetText(this Control control, string text)
        {
            if (control.InvokeRequired)
                control.Invoke(setText, control, text);
            else
                control.Text = text;
        }

        private static readonly Action<Control, string> setText =
            (control, text) => control.Text = text;
    }
}

说明:

这个答案与这个非常相似,但使用了更整洁(在我看来)和更新的语法。关键在于controlInvokeRequired属性。它获取一个值,指示调用方在对控件进行方法调用时是否必须调用Invoke方法,因为调用方位于与创建控件的线程不同的线程上。所以如果我们在创建control的相同线程上调用control.SetText("some text"),只需将Text设置为control.Text = text即可。但在任何其他线程上,它都会导致System.InvalidOperationException,因此必须通过control.Invoke(...)调用方法,在创建control的线程上设置Text


3
我更喜欢这个:
private void UpdateNowProcessing(string nowProcessing)
{
    if (this.InvokeRequired)
    {
        Action<string> d = UpdateNowProcessing;
        Invoke(d, nowProcessing);
    }
    else
    {
        this.progressDialog.Next(nowProcessing);
    }            
}

2
在我的情况下(WPF),解决方法很简单,如下所示:
private void updateUI()
{
    if (!Dispatcher.CheckAccess())
    {
        Dispatcher.BeginInvoke(updateUI);
        return;
    }

    // Update any number of controls here
}

问题是关于 Winforms。 - Marc L.

0
最简单的方法是按以下方式调用:
 Application.Current.Dispatcher.Invoke(new Action(() =>
             {
                    try
                    {
                        ///
                    }
                    catch (Exception)
                    {
                      //
                    }


                    }
     ));

1
除非问题是针对WinForms。 - LarsTech
不知道为什么这个被踩了!它帮助我解决了一个WPF应用程序中的问题。并非所有阅读此帖子的读者都会遇到与OP完全相同的问题,有时候解决方案的一部分可以在更短的时间内帮助读者,而不是提供完整的解决方案来解决一个独特的问题。 - siggi_pop

0
在WPF中,我是这样做的。
 new Thread(() => 
 {
     while (...)
     {
         SomeLabel.Dispatcher.BeginInvoke((Action)(() => SomeLabel.Text = ...));
     }
 }).Start();

5
顺便说一下,这个问题其实是关于 [winforms] 的。 - Andrew Barber

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