从四分钟到两秒:我的客户端性能优化实践

2015-01-21 16:35 稿源:博客园  1条评论

树形菜单的延迟加载

树形菜单的树形节点的构建就是一个最适合解释的例子。大家可以尝试加载1000个树形节点然后构建成一棵树,看看在Winform中需要多长的时间。我们的实际中有没有必要这么去做呢?

各位可以思考下自己查看树形导航的时候,是不是从根节点到子节点最后到叶子节点这样一步步看下去的,大部分的时候,其实我们只需首先看到根节点即可。例如下面这个:

对于这种情况,我们完全可以把树形节点都获取,但是先只创建只有根节点的一棵树,在用户点击之后加载子节点,如果已判断过,则不执行加载的操作。基本的方法是在Tag中附加一个字段指示子节点是否已经加载,参考代码如下:

  • private void TreeDevices_BeforeSelect(object sender, TreeViewCancelEventArgs e)
  • {
  • var myNode = e.Node.Tag as NTier.Model.MyTreeNode;
  • if (myNode == null) return;
  • if (myNode.IsSubNodeLoaded == false)
  • {
  • //还没有加载数据,主要是指机房节点
  • LoadNodesOfSubMainForm(e.Node, myNode); //加载树形子节点
  • }
  • //已加载了数据,则生成相应的界面
  • LoadFormModel(myNode);
  • }

这里延迟加载与按需加载有点类似,区别是,延迟加载必须把所有数据加载进来,但是并不构建成一棵UI树,而是在用到的时候再去生成。

右键延迟初始化

另一个地方就是每个控件的右键菜单。因为每个右键菜单显示的内容是需要根据控件的类型以及相关的权限来判断的,但是我们看到右键菜单的时候一定是人为进行操作才能显示出来,因此没有必要再界面生成的过程中去为每个控件生成对应的右键菜单,而是在弹出右键菜单时进行相关的判断,延迟右键菜单的生成。

化曲为直

我们知道,如果要查看一棵树的所有节点,常用的方法就是使用递归进行广度遍历或者深度遍历。但是,在树形节点较多的时候,遍历其实是非常耗时的。在我们这个系统中,告警是必须要最先处理的,因此,我在系统中使用Dictionary类型缓存了每个属性节点与它相关联的数据类型(ID),从而能够在发生告警时马上定位到指定树形节点。

缓存,还是缓存

缓存界面

我们系统是组态的界面,这就限制了界面的生成必须是动态的。如果我们采用按需加载的方式,那么界面的生成就是实时的,怎么能够做到快速的进行页面的切换呢?

  • var tempPanel = _panelCache.CreatePanel(this, formModel, myNode.AgentBm); //创建Panel

在这里,我专门写了一个界面的缓存类,如果没有缓存,则动态创建,如果有缓存,就直接返回缓存的界面。同时,根据界面的最新的打开时间和点击次数,对缓存的界面进行管理。我们知道,整个大型系统中,其实用户关注的界面也是有限的,一般他们只会关注最重要的几个界面,最常用的也是这几个界面。通过缓存的管理,不但能够实现界面之间的快速切换,同时也减少了系统占用的内存。我整个客户端程序文件大小压缩之后在500k之内,而运行期间占用内存基本维持在50M左右

缓存数据

查看上面改造过后的架构,我们知道现在获取数据是在打开界面之后再去获取,直到建立连接并取得数据之后,才能在界面上显示,这个过程一般会耗时1~2秒,网络差的情况会更糟。怎样才能让用户更为快速的确定我们的系统已经运行了呢?这里我们通过一个简单的办法,集中服务端通过定时把当前监控到的数据写入控件的属性中,在系统加载控件的同时把这个值显示出来,这样可以看起来好像是系统马上获取到了数据。而由于缓存的值是定时把最新值写入进去的,这种做法在很大程度上保证了缓存中的数值是正确的。

异步,还是异步

异步是提高程序响应和用户体验的不二法宝。C#中的控件和大部分流操作类等都提供了支持异步操作的方法:BeginXXX和EndXXX.它的原理也非常简单,使用BeginXXX时,把操作加入线程池,执行完成之后调用一个回调函数。

一个用户体验良好的系统,应该能够合理的使用异步操作,确保执行UI更新时以及执行耗时的操作时不会阻塞。大部分人在写代码的时候,总是直接进行调用,在控件较少或者完成简单任务的时候,你一般都感觉不出来,但是在控件数量多的时候,我们很容易就感觉到界面卡,不流畅。

我在新系统开发的时候,就有意识的在控件加载、控件数据刷新、控件告警状态切换等操作中使用了异步的操作,让系统在打开界面时完全感觉不到卡的迹象。

不过使用异步要时刻记得,异步可以提高用户体验性,但是不会有性能上的实质提升,如果感觉到数据响应有延迟,你还是得花功夫找到根本的原因。

归并处理

界面数据刷新归并处理

我们来看看原来界面是怎么刷新数据的:

打开界面->刷新数据->新建一个线程->定时刷新数据->关闭界面->关闭线程。

对Windows系统有足够了解的人都知道,新开一个线程都是非常耗费资源的。这种情况,我们是可以在整个系统中,提供一个统一管理的刷新线程,只需对当前需要刷新的界面进行刷新即可:

刷新线程->判断当前界面是否存在->定时刷新数据

结合上述的异步操作,我们的控件在刷新数据的时候非常的流畅。

告警跳转归并处理

上面我们提到了,在系统发生告警时,必须要跳转到报警的页面,这个机制在大量告警并发的时候,就会有非常大的问题,很可能我们的系统就会在不同的界面中进行跳转而卡死。对于系统的用户来说,在1~3秒内的多个告警,我们其实可以处理为一个告警,我们只需往最后一个告警发生的页面跳转即可,这样既达到了相应的效果,也减少了系统的压力。这就是告警并发时的归并处理。

视觉欺骗

在一些情况中,我们确实短时间没有办法对性能进行提升了,花费的时间却要要这么多,这种情况下,我们有些什么好的做法呢?

给出提示信息或者进度条

如果大家经常用手机登陆微博、微信等,肯定对这些app加载图片有过一些体会,尤其如果你是在网络较差的情况下,同样是要等1分钟才能加载出图片,如果这个app没有任何提示,那么,过了30秒或者20秒,你就有可能受不了把他点掉了,因为你感觉它似乎已经过了几分钟,还有可能遥遥无期;而如果这个app能够提示当前下载的字节数、当前下载的进度,那么,1分钟的等待,你似乎也能接受,这毕竟是网络引起的问题。这就是一种视觉上的欺骗。

在一个系统的加载过程中,有提示信息和没提示信息,有进度条和没进度条,给人感觉的速度是不一样的,即使从实际的情况来看这两者没有任何差别。

有好的文章希望站长之家帮助分享推广,猛戳这里我要投稿

相关文章

相关热点

查看更多

关闭