最近客户的数据库中的某些表的数据到达了千万级别,数据查询画面开始卡的要死了,所以项目经理将优化数据查询画面的“重任”交给了我,先放一下优化话之后的效果图
优化的原理很简单就是把数据源的查询方式从同步改成异步
改之前的代码,代码段1
private Void GetData() { //获取筛选条件 DtRaindataD rainDataEntity = GetFilter(); //获取数据 var dataSource = GetDataSource(rainDataEntity); //将数据源绑定到UI控件 BindData(dataSource); }
优化之后的代码,代码段2
private Void AsynGetData() { //获取筛选条件,与UI控件有交互 DtRaindataD rainDataEntity = GetFilter(); ThreadPool.QueueUserWorkItem(o => { //获取数据,与UI控件无交互 var dataSource = GetDataSource(rainDataEntity); //将数据源绑定到UI控件,与UI控件有交互 BindData(dataSource); }); }
经过调试,弹出个错误,错误消息“调用线程无法访问此对象,因为另一个线程拥有该对象。”。出现这个错误的原因很简单“WPF应用程序不允许非UI线程访问UI控件”。请看代码段3
private Void AsynGetData() { //获取筛选条件,与UI控件有交互 DtRaindataD rainDataEntity = GetFilter(); ThreadPool.QueueUserWorkItem(o => { //获取数据,与UI控件无交互 var dataSource = GetDataSource(rainDataEntity); Application.Current.Dispatcher.BeginInvoke( DispatcherPriority.SystemIdle, new Action(() => { //将数据源绑定到UI控件,与UI控件有交互 BindData(dataSource); })); }); }
这就结束了?NO,这只是雏形而已。
仍然存在几个问题去解决:
1、用户提交查询之后,仍然可以再次点击查询,所以在第一次点击查询之后,要将查询按钮禁用掉。
2、用户点击查询之后,在查询的画面应该出个Loading画面。已告知用户程序在查询中。
这两个问题就涉及到查询按钮A和Loading.
思考在查询操作中的每个流程
获取查询条件->禁用查询按钮A,显示Loading控件B->通过查询条件获取结果集->绑定结果集到UI展示数据控件C->启动查询按钮B,隐藏Loading控件B
得出在AsynGetData方法中需要加入哪些操作,请看以下代码段4
private Void AsynGetData() { //获取筛选条件,与UI控件有交互 DtRaindataD rainDataEntity = GetFilter(); ThreadPool.QueueUserWorkItem(o => { //显示Loading控件,禁用查询按钮 gslcLoad.Visibility = Visibility.Visible; UcDataSele.panelSelectDel.IsEnabled = false; //获取数据,与UI控件无交互 var dataSource = GetDataSource(rainDataEntity); Application.Current.Dispatcher.BeginInvoke( DispatcherPriority.SystemIdle, new Action(() => { //将数据源绑定到UI控件,与UI控件有交互 BindData(dataSource); //隐藏Loading控件,启用查询按钮 gslcLoad.Visibility = Visibility.Hidden; UcDataSele.panelSelectDel.IsEnabled = true; })); }); }
本想直接加上的,但是类似这样画面这个项目种有十几个,我是否要一个一个的加,这样岂不是控制两个控件的逻辑代码就要复制十几遍。所以我可以用模板方法模式抽取这个通用的结构,抽取得到的结构如下,断码段5
protected void AsynDisplayData(GridShowLoadingControl gslcLoad, SelectControl selectControl) { ThreadPool.QueueUserWorkItem(o => QueueWork(gslcLoad, selectControl)); } private void QueueWork(GridShowLoadingControl gslcLoad, SelectControl selectControl) { object[] filters = null; AutoResetEvent resetEvent = new AutoResetEvent(true); resetEvent.Reset(); Application.Current.Dispatcher.BeginInvoke( DispatcherPriority.SystemIdle, new Action(() => { filters = GetFilter(); gslcLoad.Visibility = Visibility.Visible; resetEvent.Set(); selectControl.panelSelectDel.IsEnabled = false; })); object[] dataSources = null; resetEvent.WaitOne(); try { dataSources = GetDataSource(filters); } catch (Exception ex) { LogCtrl.Error(Comm.CommConst.SystemService.WebOpId, logBase, string.Format("查询数据时发生错误[{0}]", ex.Message + ex.StackTrace)); Application.Current.Dispatcher.BeginInvoke( DispatcherPriority.SystemIdle, new Action(() => { gslcLoad.Visibility = Visibility.Hidden; selectControl.panelSelectDel.IsEnabled = true; DXMessageBox.Show("查询异常,详细信息请看日志!", CommConst.MessageTitle, MessageBoxButton.OK, MessageBoxImage.Warning); })); return; } Application.Current.Dispatcher.BeginInvoke( DispatcherPriority.SystemIdle, new Action(() => { gslcLoad.Visibility = Visibility.Hidden; selectControl.panelSelectDel.IsEnabled = true; BindData(dataSources); })); } protected virtual object[] GetFilter() { throw new NotImplementedException("请覆写GetFilter方法!"); } protected virtual object[] GetDataSource(params object[] state) { throw new NotImplementedException("请覆写GetDataSource方法!"); } protected virtual void BindData(params object[] state) { throw new NotImplementedException("请覆写BindData方法!"); }
子类查询画面通过覆写GetFilters、GetDataSource、BindData三个虚方法就可以了。
这次实践就到这里了,细心的园友发现通用的结构中多了一个AutoResetEvent对象,并且我的代码中假设了BaseWindow的存在,如果BaseWindow不存在怎么办,是否有其他的方法呢。如果园友有兴趣的话,我可以把这部分内容放在下篇博文中。