一次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