一、引言
我们知道不管什么语言,什么框架WinForm,WPF,MFC也好,子线程都不能直接去更新UI中的控件。1是为了保证UI的流畅,2是UI线程是没有对象锁的,如果有其他线程同时去操作的话,容易造成不可预期的结果。
MFC中我们一般都是发送自定义消息SendMessage或者PostMessage去通知UI需要更新某些控件元素了,发送的消息会到UI线程自己的消息队列里,UI线程通过消息循环,循环到自定义消息后会更新特定的UI元素。MFC中这种实现原理比较接近WINAPI,如果完全理解了,那不管其他什么语言都非常容易理解,一通百通。
二、.NET用法示例
1.winform方法一:Invoke方法
//子线程
private void WorkThread1()
{
while (true)
{
UpdateLabel1(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Thread.Sleep(500);
}
}
private void UpdateLabel1(string msg)
{
//如果调用控件的线程和创建创建控件的线程不是同一个则为True
if (this.label1.InvokeRequired)
{
this.label1.Invoke(new Action<string>(UpdateLabel1), new object[] { msg });
}
else
{
this.label1.Text = msg;
}
}
这种方法不是很简洁,比较啰嗦
2.winform方法二:BeginInvoke方法
private void WorkThread()
{
while (true)
{
this.label1.BeginInvoke((MethodInvoker)delegate
{
label1.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
});
Thread.Sleep(500);
}
}
3.wpf方法一:Invoke方法
//启动子线程
private void Button_Click(object sender, RoutedEventArgs e)
{
Thread thread = new Thread(ModifyUI);
thread.Start();
}
//更新UI线程创建的对象
private void ModifyUI()
{
// 模拟一些工作正在进行
Thread.Sleep(TimeSpan.FromSeconds(2));
this.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate ()
{
label1.Content = "欢迎你光临WPF的世界,Dispatcher";
});
}
4.wpf方法二:BeginInvoke方法,基本同Invoke方法
private void ModifyUI()
{
// 模拟一些工作正在进行
Thread.Sleep(TimeSpan.FromSeconds(2));
this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate ()
{
label1.Content = "欢迎你光临WPF的世界,Dispatcher";
});
}
DispatcherPriority定义了很多优先级:
5.wpf方法三:使用BackgroupWorker组建
这个组建在winform,wpf中都有,专门为简化子线程和UI交互而设计的,比如实现了进度通知,支持取消,完成通知等。
用法很简单,如下:
//创建BackgroundWorker实例
BackgroundWorker backgroundWorker;
backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += backgroundWorker_DoWork;
backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
//可以返回工作进度
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.ProgressChanged += backgroundWorker_ProgressChanged;
//支持取消
backgroundWorker.WorkerSupportsCancellation = true;
//开始执行DoWork
backgroundWorker.RunWorkerAsync();
三、最后
在wpf中DispatcherObject对象只能被创建它的线程所访问,其他线程修改 DispatcherObject需要取得对应的Dispatcher,调用Invoke或者BeginInvoke来投入任务。Dispatcher的一些设计思路包括 Invoke和BeginInvoke等从WinForm时代就是一直存在的,只是使用了Dispatcher来封装这些线程级的操作。