一次WPF異步編程的實踐1

最近客戶的數據庫中的某些表的數據到達了千萬級別,數據查詢畫面開始卡的要死了,因此項目經理將優化數據查詢畫面的「重任」交給了我,先放一下優化話以後的效果圖數據庫

優化的原理很簡單就是把數據源的查詢方式從同步改爲異步異步

改以前的代碼,代碼段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

相關文章
相關標籤/搜索