从另一个线程更新标签的最简单方法是什么?
我在thread1上运行了一个Form,然后我开始另一个线程(thread2)。当thread2处理一些文件时,我想用thread2工作的当前状态更新表单上的标签。
我怎么能这样做?
从另一个线程更新标签的最简单方法是什么?
我在thread1上运行了一个Form,然后我开始另一个线程(thread2)。当thread2处理一些文件时,我想用thread2工作的当前状态更新表单上的标签。
我怎么能这样做?
当前回答
只使用ui的同步上下文
using System.Threading;
// ...
public partial class MyForm : Form
{
private readonly SynchronizationContext uiContext;
public MyForm()
{
InitializeComponent();
uiContext = SynchronizationContext.Current; // get ui thread context
}
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(() =>
{// set ui thread context to new thread context
// for operations with ui elements to be performed in proper thread
SynchronizationContext
.SetSynchronizationContext(uiContext);
label1.Text = "some text";
});
t.Start();
}
}
其他回答
关于这个主题的另一个示例:我创建了一个抽象类UiSynchronizeModel,它包含一个公共方法实现:
public abstract class UiSynchronizeModel
{
private readonly TaskScheduler uiSyncContext;
private readonly SynchronizationContext winformsOrDefaultContext;
protected UiSynchronizeModel()
{
this.winformsOrDefaultContext = SynchronizationContext.Current ?? new SynchronizationContext();
this.uiSyncContext = TaskScheduler.FromCurrentSynchronizationContext();
}
protected void RunOnGuiThread(Action action)
{
this.winformsOrDefaultContext.Post(o => action(), null);
}
protected void CompleteTask(Task task, TaskContinuationOptions options, Action<Task> action)
{
task.ContinueWith(delegate
{
action(task);
task.Dispose();
}, CancellationToken.None, options, this.uiSyncContext);
}
}
模型或控制器类应从此抽象类派生。您可以使用任何模式(任务或手动管理的后台线程)并使用以下方法:
public void MethodThatCalledFromBackroundThread()
{
this.RunOnGuiThread(() => {
// Do something over UI controls
});
}
任务示例:
var task = Task.Factory.StartNew(delegate
{
// Background code
this.RunOnGuiThread(() => {
// Do something over UI controls
});
});
this.CompleteTask(task, TaskContinuationOptions.OnlyOnRanToCompletion, delegate
{
// Code that can safely use UI controls
});
我认为最简单的方法是:
void Update()
{
BeginInvoke((Action)delegate()
{
//do your update
});
}
您必须确保更新发生在正确的线程上;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类,它将为您抽象这个问题。
在我的案例(WPF)中,解决方案简单如下:
private void updateUI()
{
if (!Dispatcher.CheckAccess())
{
Dispatcher.BeginInvoke(updateUI);
return;
}
// Update any number of controls here
}
这一解决方案类似于上面使用.NET Framework 3.0的解决方案,但它解决了编译时安全支持的问题。
public static class ControlExtension
{
delegate void SetPropertyValueHandler<TResult>(Control souce, Expression<Func<Control, TResult>> selector, TResult value);
public static void SetPropertyValue<TResult>(this Control source, Expression<Func<Control, TResult>> selector, TResult value)
{
if (source.InvokeRequired)
{
var del = new SetPropertyValueHandler<TResult>(SetPropertyValue);
source.Invoke(del, new object[]{ source, selector, value});
}
else
{
var propInfo = ((MemberExpression)selector.Body).Member as PropertyInfo;
propInfo.SetValue(source, value, null);
}
}
}
要使用:
this.lblTimeDisplay.SetPropertyValue(a => a.Text, "some string");
this.lblTimeDisplay.SetPropertyValue(a => a.Visible, false);
如果用户传递错误的数据类型,编译器将失败。
this.lblTimeDisplay.SetPropertyValue(a => a.Visible, "sometext");