一次WPF異步編程的實踐1


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

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

改之前的代碼,代碼段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不存在怎么辦,是否有其他的方法呢。如果園友有興趣的話,我可以把這部分內容放在下篇博文中。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM