从另一个线程更新标签的最简单方法是什么?
我在thread1上运行了一个Form,然后我开始另一个线程(thread2)。当thread2处理一些文件时,我想用thread2工作的当前状态更新表单上的标签。
我怎么能这样做?
从另一个线程更新标签的最简单方法是什么?
我在thread1上运行了一个Form,然后我开始另一个线程(thread2)。当thread2处理一些文件时,我想用thread2工作的当前状态更新表单上的标签。
我怎么能这样做?
当前回答
您可以使用现有的委托操作:
private void UpdateMethod()
{
if (InvokeRequired)
{
Invoke(new Action(UpdateMethod));
}
}
其他回答
绝大多数答案使用Control.Invoke,这是等待发生的竞争条件。例如,考虑接受的答案:
string newText = "abc"; // running on worker thread
this.Invoke((MethodInvoker)delegate {
someLabel.Text = newText; // runs on UI thread
});
如果用户在调用this.Invoke之前关闭表单(请记住,这是form对象),则可能会触发ObjectDisposedException。
解决方案是使用SynchronizationContext,特别是hamilton.danielb建议的SynchronizaionContext.Current(其他答案依赖于特定的Synchronization Context实现,这是完全不必要的)。我会稍微修改他的代码,使用SynchronizationContext.Post而不是SynchronizazationContext.Send(因为通常不需要工作线程等待):
public partial class MyForm : Form
{
private readonly SynchronizationContext _context;
public MyForm()
{
_context = SynchronizationContext.Current
...
}
private MethodOnOtherThread()
{
...
_context.Post(status => someLabel.Text = newText,null);
}
}
请注意,在.NET4.0及更高版本上,您应该真正将任务用于异步操作。有关等效的基于任务的方法(使用TaskScheduler.FromCurrentSynchronizationContext),请参见n-san的答案。
最后,在.NET 4.5及更高版本上,您还可以使用Progress<T>(它基本上在创建时捕获SynchronizationContext.Current),正如Ryszard Dżegan所演示的,用于长时间运行的操作需要在运行时运行UI代码的情况。
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()。
还有另一个通用控件扩展。。
首先为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);
}
}
并从另一个线程调用类似的方法来访问UI线程中名为object1的控件:
object1.InvokeIfRequired(c => { c.Visible = true; });
object1.InvokeIfRequired(c => { c.Text = "ABC"; });
..或类似的
object1.InvokeIfRequired(c =>
{
c.Text = "ABC";
c.Visible = true;
}
);
您需要在GUI线程上调用该方法。您可以通过调用Control.Invoke来实现这一点。
例如:
delegate void UpdateLabelDelegate (string message);
void UpdateLabel (string message)
{
if (InvokeRequired)
{
Invoke (new UpdateLabelDelegate (UpdateLabel), message);
return;
}
MyLabelControl.Text = message;
}
即使操作很耗时(在我的示例中为thread.sleep)-此代码不会锁定您的UI:
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;
});
}