WPF&MVVM线程问题(progressbar为例)

来源:互联网 发布:点胶机编程视频 编辑:程序博客网 时间:2024/06/11 04:57

WPF&MVVM线程问题


别让能力撑不起野心

  • WPFMVVM线程问题
    • 后台UI线程
    • 一般线程交互
      • a首次修改引入Dispatcher
      • b再次修改引入Task
      • c最后修改
    • MVVM线程交互
    • 源码下载

后台,UI线程

专业解释我就不贴,说说自己的个人愚见,线程有后台,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,(线程工厂,底层是线程池);

a.首次修改(引入Dispatcher)

直接上代码了

 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线程,也是主线程。

b.再次修改(引入Task)

老规矩,先上代码:

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传递出错。

c.最后修改

聪明的人应该已经知道接下来就是两者一起运用了:

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的教程还是不多,而且一般都是边开发边学习得来的知识,包括自己,难免会缺三补四。
关于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。

1 0
原创粉丝点击