• [自带避雷针]DropShadowEffect导致内存暴涨


                              [自带避雷针]DropShadowEffect导致内存暴涨

                                                     周银辉 

    从学习WPF开始, 就知道"位图效果"不是什么省油的灯, 但我只知道它会占用很多cpu时间, 而显得效率低下, 但完全不料的是在某种特殊的硬件环境下其会导致内存暴涨.

    事情是这样的, 我写了一个小程序, 看起来还不错, 但并不变态的日本朋友在变态的硬件环境下测试我的程序说"操作一段时间后程序会崩溃". 我远程VNC过去,发现程序每进行一个小操作(甚至是点击一下鼠标)内存会增长好几M,  照这个速度, 一会准崩溃, NND

    但, 郁闷的事情是, 我无法重现这个Bug, 在开发团队和测试团队的机器上都没法重现(但在日本那边,这个bug是百分百重现)...内存泄露还没法重现, 好搞笑...更搞笑的事情在后面

    最后我发现日本那边采用了如下的显示器配置: 其主机上连接了3个显示器, 第一个为普通屏, 第二和第三个屏是医学专用的高清屏(传说分辨率超高,当然价格也超贵), 当连接上这两个屏幕的时候, 我的程序就死翘翘了. 首先排除单纯的多屏问题,因为我的开发机就是双屏的, 然后我又排除了高清屏的问题, 因为禁用掉普通屏,而使用高清屏的时候,一切OK.

    经过一系列的排查, 得到的诡异结论是: 当普通屏和高清屏同时启用, 并且我的程序窗口显示在普通屏上的时候, 内存暴涨, 最后死翘翘, 其他情况一切OK...

    为什么要在这么诡异的环境下才能重现这个问题呢, 完全无解.

    好吧, 我暂且承认是自己代码烂, 内存泄露了吧, 还好我有  .net memory profile ANTS Memory Profiler 

    经过一番折腾之后, 这两个被我奉为"神器"的内存工具并没有给我任何答案: 从变化曲线上看内存是增长了许多(动不动就上百兆), 但内存快照中各个类型对象的内存占有率以及变化率都非常地正常, 晕, 丢失的内存哪里去了~~

    我开始怀疑是P/Invoke之类的丢失了内存, 因为程序中有大量的win32 API平台调用, 然后,我注释掉了这些代码... 很不幸, 答案是NO.

    没辙, 再来一招: 功能裁剪,这是经常使用的一招, 这让我们比较容易地缩小代码范围. 由于UI层和后台代码的耦合度非常低, 直接注释掉UI层上的XAML代码, 功能就能被很好地裁掉, 而不用更改逻辑代码, 所以功能裁剪显得比较容易. 我的程序都快被裁成空壳了, 问题依然存在... 
    还有一个很搞怪的问题是, 比如我们定位到一个比较复杂的函数A会导致问题, 然后从A开始跟踪, 到B -> C -> D 绕了很长一圈后, 会到达一个及其简单的代码上比如 : this.width = myWidth; 注释到这个简单的代码就没问题, 否则导致问题, 诶, 难道是宽度的改变会引发什么事件,或者重写了控件重绘等等 而导致的问题? 这一点点希望很快就被抹杀了, 控件已经被我们抽取到足够的简单, 几乎没有什么代码.... 耍我么... 

    一般, 在我绝望的时候, 我会使用我的大绝招: 我猜 

    我一直比较相信写程序还是要靠灵感的(当然, 建立在你基础知识比较扎实的基础之上的). 那么我先猜是那个控件导致的问题呢, 我当然不会怀疑自己的代码咯, 我怀疑微软的代码, 这不是自大, 而是经验值, 我搞了不少无厘头的问题, 最后根源都在微软的代码上, 如果你有机会玩玩微软的RichTextBox控件再加上一个日文的Atok输入法, 你就会相信我说的话的(会搞死人的). 所以我猜ViewBox, 程序中的一个绘图面板放在ViewBox中(为实现缩放功能), 那好, 把绘图板从面板中剪切出来吧.

    哈哈, OK啦,  把绘图板从面板中剪切出来后真的没问题也, 难道我运气这么好, 一下就猜中了...

    为了验证, 我重新做了一个很简单的关于viewbox的DEMO, 放在日本那边的机器上, 没问题, 我晕

    然后我注意到源程序中, viewbox放在一个ScrollViewer中, 并且这个ScrollViewer有自己的模板,  もしかしてなの ...

    把画图板重新放回到ViewBox中, 在scrollViewer中动刀. 我发现, 我惊奇地发现:
     

    在该ScrollViewer控件模板中, 居然有一个DropShadowEffect, 天杀的. 另外, 这个effect在我们的程序里面看不到阴影效果, 我们的程序也不需要这个效果, 所以一直没察觉.

    删掉! 整个世界都安静了~

    为了证明这个的确会导致问题, 所以我做了一个简单的DEMO,  

    <Window x:Class="DropShadowEffectMomeryLeakDemo.Window1"
        xmlns
    ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x
    ="http://schemas.microsoft.com/winfx/2006/xaml"
        Title
    ="Window1" Height="300" Width="300">
        
    <Canvas x:Name="canvas">
            
    <Canvas.Effect>
                
    <DropShadowEffect/>
            
    </Canvas.Effect>
            
    <Button Width="100" Height="30" Content="Click me" Click="Button_Click"/>
            
            
    <!--下面这些代码可以忽略, 我只是放了点普通控件,以便上内存泄露更明显-->
            
    <Rectangle Fill="White" Stroke="Black" Width="53" Height="43" Canvas.Left="37" Canvas.Top="71"/>
            
    <Rectangle Fill="#FFAF8C8C" Stroke="Black" Width="54" Height="72" Canvas.Left="158" Canvas.Top="42"/>
            
    <Rectangle Fill="White" Stroke="Black" Width="54" Height="47" Canvas.Left="117" Canvas.Top="130"/>
            
    <Rectangle Fill="#FF21CB63" Stroke="Black" Width="44" Height="65" Canvas.Left="212" Canvas.Top="130"/>
            
    <Rectangle Fill="White" Stroke="Black" Width="42" Height="49" Canvas.Left="58" Canvas.Top="177"/>
            
    <Rectangle Fill="White" Stroke="Black" Width="53" Height="45" Canvas.Left="171" Canvas.Top="195"/>
            
    <Rectangle Fill="#FFDE13EE" Stroke="Black" Width="70" Height="69" Canvas.Left="224" Canvas.Top="30"/>
            
    <Rectangle Fill="White" Stroke="Black" Width="21" Height="43" Canvas.Left="100" Canvas.Top="42"/>
            
    <Rectangle Fill="White" Stroke="Black" Width="21" Height="22" Canvas.Left="171" Canvas.Top="8"/>
            
    <Rectangle Fill="White" Stroke="Black" Width="37" Height="43" Canvas.Left="117" Canvas.Top="71"/>
            
    <Rectangle Fill="White" Stroke="Black" Width="41" Height="33" Canvas.Left="49" Canvas.Top="130"/>
            
    <Rectangle Fill="#FFFF2B2B" Stroke="Black" Width="54" Height="31" Canvas.Left="100" Canvas.Top="195"/>
        
    </Canvas>
    </Window>

     然后点击Button的时候,做如下操作:

            private void Button_Click(object sender, RoutedEventArgs e)
            {
                
    for(int i=0; i<canvas.Children.Count; i++)
                {
                    var c 
    = (FrameworkElement) canvas.Children[i];
                    c.Width 
    += 1;
                    c.Height 
    += 1;
                }
            }

    在我的开发机上(在你的机器上可能也是), 连续点击button, 内存会有所增长, 但增长比较慢, 并且过一小会就释放带了, 所以内存基本维持到十多兆(图中18756K):
     

    放在日本那个特殊的环境下,情况就完全不一样了(下图99340K, 并且好玩的是, 下图有黄色桌面背景的是第一个显示器, 蓝色背景的是第二个显示器, 把window1拖到第二个显示器上内存表现会很正常哦) :
     

     恩, 人生在于折腾

    ps: 这台机器插了两张显卡: nVIDIA Quadro FX370 和 VREngine SMD5

  • 相关阅读:
    线程同步技术
    线程调用
    进程与线程
    网络配置
    vi
    文件系统
    系统管理命令
    Linux常用命令
    Shell编程
    新版chrome touch警告处理办法
  • 原文地址:https://www.cnblogs.com/zhouyinhui/p/1757277.html
Copyright © 2020-2023  润新知