别让能力撑不起野心
专业解释我就不贴,说说自己的个人愚见,线程有后台,UI(前台)之分,UI元素所使用的线程为UI线程,其他的可以理解为后台线程。
区别:程序要关闭,必须等待UI线程终止,而不用等待后台线程终止。(这个也是为什么有时候我们的界面会卡死,但也关闭不了的原因)
举个例子:
界面上做个按钮ProgressBar,和Button,按钮click设置点击事件:
private void Button_Click(object sender, RoutedEventArgs e)
{
for(int j = 0; j < 100; j++)
{
this.progressbar.Value = j;
System.Threading.Thread.Sleep(100);
}
}
按钮点击后,我们会发现窗口卡住了,今天我们就是要处理这样的问题。
有了上面抛的砖,下面我们继续捡砖(常规WPF的线程交互)。
先说说所使用的技术:
1.Dispatcher(UI),线程调度器;
2.Task.Factory,(线程工厂,底层是线程池);
直接上代码了
private void Button_Click(object sender, RoutedEventArgs e)
{
for (int j = 0; j < 100; j++)
{
this.progressbar.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background, (Action)delegate () {
this.progressbar.Value = j;
});
System.Threading.Thread.Sleep(100);
}
}
这里通过this.progressbar.Dispatcher来获取UI线程的调度器,然后使用异步方法更新UI,
关于Dispatcher的用法可以参考这里Dispatcher
实际上是,这里并没有起任何作用,那么就有疑问:为什么我调用了UI线程更新UI却不起作用。
原因在于,我们这个事件的拥有者是sender,而这里的sender就是Button,UI元素,其实这里本身就已经是UI线程,也是主线程。
老规矩,先上代码:
private void Button_Click(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(() =>
{
for (int j = 0; j < 100; j++)
{
this.progressbar.Value = j;
System.Threading.Thread.Sleep(100);
}
});
}
这里加入了Task.Factory的用法,关于Task.Factory的用法可以参考这里Task.Factory
编译通过,可是运行时报错,在 this.progressbar.Value = j 时报
“调用线程无法访问此对象,因为另一个线程拥有该对象。”
我们startnew了一个新线程,但是在这个线程中操作UI对象(不是UI线程,这里要注意),对象j传递出错。
聪明的人应该已经知道接下来就是两者一起运用了:
private void Button_Click(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(() =>
{
for (int j = 0; j < 100; j++)
{ this.progressbar.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background, (Action)delegate () { this.progressbar.Value = j; });
System.Threading.Thread.Sleep(100);
}
});
}
这里结合使用,具体的意思是:在新线程(后台线程)中进行运行运算(sleep(100)模拟运算),在UI线程进行小更新(progressbar),各司其职,合理分配。
关于mvvm的使用,已经不是新鲜事了,但是系统的mvvm的教程还是不多,而且一般都是边开发边学习得来的知识,包括自己,难免会缺三补四。
关于MVVM模式的说明和使用,个人推荐一下msdn说明和devexpress应用&代码
吐槽的话少说,上硬菜。
1.mvvm的滥用:
为什么这么说,自己之前的一个愚见,通过在ViewModel中设置相关的属性变量,命令command,事件event,然后让View的控件进行绑定,通过后台修改ViewModel变量的属性,实现更新UI。这样一来,界面增加控件,ViewModel增加相应的变量,命令,事件,而不用再View的cs后台进行修改。
初期,这是个还可以接受的做法,到了后期,会发现,这个ViewModel十分的庞大,维系成本增加,要是代码转接,这也是一个需要学习了解的过程。(关于这点,还没有学习到相关的资料,只是自己想象减轻工作的思路,自己也是这么做的,就是界面控件化,界面拆分成细致的控件,让控件独立绑定ViewModel,今天问题不在这里)
2.ViewModel不方便的地方:
ViewModel可以实现绝大部分的界面交互,不过,总会有些奇奇怪怪的功能让你懊恼。比如:按钮实现窗体退出功能,子窗体跳转功能,部分控件的部分属性修改。
这些功能,对于winform老手,没有多少选择,直接在View后台几行代码就实现了,而选择了使用MVVM,让窗体的closed属性bignding,这个还真没有试过吧(其实窗体还没有closed属性,只有closed事件)。
3.MVVM线程交互:
以上两点纯属是自己开发过程中的一些牢骚总结,回来正式话题。
ViewModel中的Command实际上是后台线程,而非UI线程,与click事件之类的事件不同,而bingding的属性都会有一个PropertyChanged的方法通知View,它控件bingding的这个属性改变了,让控件刷新数据,这里实质上是UI线程的交互,相关理论知识还得查看INotifyPropertyChanged接口描述。
现在的问题来了,如果我一个控件,没有实现bingding,但我想操作它的属性,该怎么办,这个就是本章的主要问题,如果你正在做相关的工作,从这里直接看就可以了。
所使用的技术点:
1.EventHandler;
2.Dispatcher(同上描述);
ViewModel的代码:
public event EventHandler CloseProgressBarEvent;
public event EventHandler CloseWndAndShowGameView;
public ICommand PlusCount { get; set; }
…………
public MainViewModel()
{
…………
this.PlusCount = new DelegateCommand(this.plusCount);
…………
}
private void plusCount()
{
…………
var dispatcher = App.Current.MainWindow.Dispatcher;
dispatcher.BeginInvoke((Action)delegate () { CloseProgressBarEvent?.Invoke(this, tmpArgs); });
Task.Factory.StartNew(() =>
{
ShowValue = 0;
System.Threading.Thread.Sleep(2000);
while (ShowValue < 100)
{
ShowValue += 1;
System.Threading.Thread.Sleep(100);
tmpArgs._message = $"the program precent is {ShowValue.ToString()}";
dispatcher.BeginInvoke((Action)delegate () { CloseProgressBarEvent?.Invoke(this, tmpArgs); });
}
dispatcher.Invoke((Action)delegate () { CloseWndAndShowGameView?.Invoke(this, new EventArgs()); });
});
}
这里关键的代码就是var dispatcher = App.Current.MainWindow.Dispatcher获取当前窗口线程,让窗体线程触发EventHandler的操作,下面来看看EventHandler 的代码:
public MainWindow()
{
InitializeComponent();
var vm = new MainViewModel();
vm.CloseProgressBarEvent += Vm_CloseProgressBarEvent;
vm.CloseWndAndShowGameView += Vm_CloseWndAndShowGameView; ;
this.DataContext = vm;
}
private void Vm_CloseWndAndShowGameView(object sender, System.EventArgs e)
{
GameView newWnd = new GameView();
newWnd.Show();
this.Close();
}
private void Vm_CloseProgressBarEvent(object sender, System.EventArgs e)
{
MyEventArgs tmp = e as MyEventArgs;
this.textBlock.Text = tmp._message;
}
大家先别吐槽,这event是有点粗暴,但是,能让你一眼就看懂的代码,也就这样了,对,就是这么简单粗暴。直接的使用this来操作UI界面。
这里也使用到了Task.Factory,Dispatcher的异步,同步方法,这就是我们一般线程交互提到的方法。
最后,源码在这里Dispatcher。
最近发现CSDN资源已经不能免费了,在这里补充百度云的链接:Dispatcher。
本文最后修改时间:2017年8月12日16:10:11。