zoukankan      html  css  js  c++  java
  • 大数据量填充UI的技巧

            这个内容本来已经在我过去的一篇随笔中略有提及,但是没有详细的说明这个问题。今天我就这个问题详细的讨论一下。这个问题的提出其实是一个源于一个设计缺陷或者错误。我所在的公司开发了一个财务系统,这个系统中有一张报表,这个报表的主要功能是显示数据检索的结果。由于在设计之初没有考虑分页处理,所以导致客户没有输入检索条件的时候,需要在界面上显示几万到几十万条记录。由于数据量非常大,导致UI的填充时间非常的漫长,为了提高UI填充的速度,采用了异步处理的方式,最初的处理方式如下:
     1 '数据传送事件
     2 Sub DataRowTransport(ByVal sender As ObjectByVal e As DataRowTransportEventArgs)
     3 
     4     '判断是否要进行线程调度
     5     If Me.InvokeRequired Then
     6 
     7         '调度线程
     8         Me.BeginInvoke(New DataRowTransportHandler(AddressOf DataRowTransport), New Object() {sender, e})
     9     Else
    10 
    11         '将数据填充到Grid
    12         Me.FillGrid(e.Data)
    13 
    14         '响应事件处理
    15         Application.DoEvents()
    16 
    17         '判断是否退出
    18         If Me.Cancel Then
    19 
    20             e.Cancel = True
    21         End If
    22     End If
    23 End Sub
            从代码的角度来看这段代码是没有什么问题的,基本上是微软提出的线程调度的最佳实践了,但是在实际中运用效果很不理想:界面不能刷新也不能响应取消操作(用户可以选择取消填充)。其实仔细想一想也不难明白,由于数据量很大,导致调用BeginInvoke的调用次数很大,而且两次之间的调用间隔很短,基本上把UI线程的消息循环给填满了,几乎没有机会来响应界面的操作了。
    经过分析之后,发现了一个较好的解决方案,代码如下:
     1     '数据缓存
     2     Private _buffer As New ArrayList
     3 
     4     '数据填充队列
     5     Private _dataQueue As New Queue
     6 
     7     '数据传送事件,由数据传输线程触发
     8     Sub DataRowTransport(ByVal sender As ObjectByVal e As DataRowTransportEventArgs)
     9 
    10         '添加到缓存
    11         Me._buffer.Add(e.Data)
    12 
    13         '判断是否需要添加到填充队列中
    14         If Me._buffer.Count > 100 Then
    15 
    16             '锁定
    17             SyncLock Me._dataQueue
    18 
    19                 '添加到队列中
    20                 Me._dataQueue.Enqueue(Me._buffer.ToArray())
    21             End SyncLock
    22 
    23             '清除数据
    24             Me._buffer.Clear()
    25         End If
    26 
    27         '判断是否退出
    28         If Me.cancel Then
    29 
    30             e.Cancel = True
    31         End If
    32     End Sub
    33 
    34     '填充数据,在UI线程调用
    35     Sub FillData()
    36 
    37         '用于保存临时数据
    38         Dim tempData As Object
    39 
    40         While (True)
    41 
    42             '设置数据为空
    43             tempData = Nothing
    44 
    45             '判断是否取消
    46             If Me.cancel Then
    47 
    48                 '退出循环
    49                 Exit While
    50             End If
    51 
    52             '判断是否有数据
    53             If Me._dataQueue.Count > 0 Then
    54 
    55                 SyncLock Me._dataQueue
    56 
    57                     '判断是否有数据,检查两次尽可能的避免同步线程
    58                     If Me._dataQueue.Count > 0 Then
    59 
    60                         '填充界面
    61                         tempData = Me._dataQueue.Dequeue()
    62                     End If
    63 
    64                 End SyncLock
    65 
    66             End If
    67 
    68             '判断是否有数据
    69             If Not tempData Is Nothing Then
    70 
    71                 '填充界面
    72                 Me.FillGrid(tempData)
    73             End If
    74 
    75             '处理消息循环
    76             Application.DoEvents()
    77 
    78         End While
    79 
    80     End Sub
    经过实际测试,这种解决方案几乎不会延迟数据读取线程,总的时间几乎不到第一种方案的一半,而且正常的响应UI事件。通过这个事件说明,通过BeginInvoke的方式将后台线程操作调度到UI线程其实性能是比较差的(经过实际测试使用BeginInvoke调度操作UI的耗费的时间为直接跨线程不同步的操作UI耗费时间的两倍,而直接跨线程不同步调用UI耗费的时间是在UI线程中调用同一个方法的2被),在常规情况下应用还可以,但是一旦数据量很大,或者并发非常大的情况下,就需要考虑用其它的手段来处理了


  • 相关阅读:
    Java 学习笔记之 线程interrupted方法
    Java 学习笔记之 线程interrupt方法
    定义函数
    调用函数
    pass语句
    循环
    条件语句
    别样赋值
    import语句
    字典方法
  • 原文地址:https://www.cnblogs.com/zengezenge/p/836658.html
Copyright © 2011-2022 走看看