从另一个线程更新标签的最简单方法是什么?
我在thread1上运行了一个Form,然后我开始另一个线程(thread2)。当thread2处理一些文件时,我想用thread2工作的当前状态更新表单上的标签。
我怎么能这样做?
从另一个线程更新标签的最简单方法是什么?
我在thread1上运行了一个Form,然后我开始另一个线程(thread2)。当thread2处理一些文件时,我想用thread2工作的当前状态更新表单上的标签。
我怎么能这样做?
当前回答
您必须确保更新发生在正确的线程上;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类,它将为您抽象这个问题。
其他回答
这是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”的结果中。它提高了静态类型的安全性。
否则,原始版本是一个非常好的解决方案。
您必须确保更新发生在正确的线程上;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类,它将为您抽象这个问题。
绝大多数答案使用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代码的情况。
最简单的方法是调用如下:
Application.Current.Dispatcher.Invoke(new Action(() =>
{
try
{
///
}
catch (Exception)
{
//
}
}
));
简单的解决方案是使用Control.Invoke。
void DoSomething()
{
if (InvokeRequired) {
Invoke(new MethodInvoker(updateGUI));
} else {
// Do Something
updateGUI();
}
}
void updateGUI() {
// update gui here
}