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

原文: [自带避雷针]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

猜你喜欢

转载自www.cnblogs.com/lonelyxmas/p/9543190.html