最近客戶的數據庫中的某些表的數據到達了千萬級別,數據查詢畫面開始卡的要死了,因此項目經理將優化數據查詢畫面的「重任」交給了我,先放一下優化話以後的效果圖數據庫
優化的原理很簡單就是把數據源的查詢方式從同步改爲異步異步
改以前的代碼,代碼段1優化
private Void GetData() { //獲取篩選條件 DtRaindataD rainDataEntity = GetFilter(); //獲取數據 var dataSource = GetDataSource(rainDataEntity); //將數據源綁定到UI控件 BindData(dataSource); }
優化以後的代碼,代碼段2spa
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,這只是雛形而已。3d
仍然存在幾個問題去解決:
一、用戶提交查詢以後,仍然能夠再次點擊查詢,因此在第一次點擊查詢以後,要將查詢按鈕禁用掉。
二、用戶點擊查詢以後,在查詢的畫面應該出個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不存在怎麼辦,是否有其餘的方法呢。若是園友有興趣的話,我能夠把這部份內容放在下篇博文中。code