从另一个线程更新标签的最简单方法是什么?
我在thread1上运行了一个Form,然后我开始另一个线程(thread2)。当thread2处理一些文件时,我想用thread2工作的当前状态更新表单上的标签。
我怎么能这样做?
从另一个线程更新标签的最简单方法是什么?
我在thread1上运行了一个Form,然后我开始另一个线程(thread2)。当thread2处理一些文件时,我想用thread2工作的当前状态更新表单上的标签。
我怎么能这样做?
当前回答
这是一个古老问题的新视角,使用了更实用的风格。如果您在所有项目中都保留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;
}
}
其他回答
我想添加一个警告,因为我注意到一些简单的解决方案忽略了InvokeRequired检查。
我注意到,如果您的代码在创建控件的窗口句柄之前(例如,在显示表单之前)执行,Invoke会抛出异常。因此,我建议在调用Invoke或BeginInvoke之前始终检查InvokeRequired。
Marc Gravell的.NET 4最简单解决方案的变体:
control.Invoke((MethodInvoker) (() => control.Text = "new text"));
或者改用Action委托:
control.Invoke(new Action(() => control.Text = "new text"));
请参阅此处了解两者的比较:MethodInvoker与Action for Control.BeginInvoke
将一些公共变量放在一个单独的类中以保存值。
例子:
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";
}
}
创建类变量:
SynchronizationContext _context;
在创建UI的构造函数中设置它:
var _context = SynchronizationContext.Current;
要更新标签时:
_context.Send(status =>{
// UPDATE LABEL
}, null);
一般方法如下:
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;
}
}
说明:
答案很像这个。但使用了更整洁(对我来说)和更新的语法。点是控件的InvokeRequired属性。它获取一个值,该值指示调用方在对控件进行方法调用时是否必须调用调用方法,因为调用方所在的线程与创建控件的线程不同。因此,如果我们在创建控件的同一线程上调用control.SetText(“一些文本”),则只需将text设置为此控件即可。text=text。但在任何其他线程上,它都会导致System.InvalidOperationException,因此必须通过控件调用方法。Invoke(…)在创建的线程控件上设置Text。