在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
一天,我收到了一封有关我的博客的邮件,提出如下问题,简述如下: 我想快速地创建一个站点地图,因此我重写了BuildSiteMap()方法,在里面我写了一个循环,用以添加一些仿造的sitemap节点。 public override SiteMapNode BuildSiteMap(){ 运行程序,就发生堆栈溢出,服务器也崩溃了。我用调试器单步调试,发现真的很奇怪: 1) int i = 0 i的值看起来从来没有增加,除非我调用到SiteMapNode(access a property, call a method),看起来这个循环是正确的。 是什么使得这个循环不确定呢?咋看可能是编译器或者是CLR的一个bug. (当我获此问题时,我真不知道ASP.NET2.0中的站点导航,但我找到了这些文章... http://weblogs.asp.net/scottgu/archive/2005/11/20/431019.aspx 和http://aspnet.4guysfromrolla.com/articles/111605-1.aspx ,叙述得真是很不错.) 最初的想法 这个问题最重要的就是它始终重新开始, 这就意味着可以对此做现场调试。但我们暂不走那么远,先回头看看现在有什么... 1. 买卖IC网堆栈溢出 2. 买卖IC网一次又一次重新开始的循环 我已经在先前的博客帖子里讨论过堆栈溢出,现在重复一下... 引起堆栈溢出的原因是, 分配了太多的函数指针,变量指针和参数,以致在堆栈里申请的内存数量不够用。到目前为止,堆栈溢出最平常的原因是无终止的递归。换句话说,function A调用了function B, function B又调用了function A... 因此,callstack看上去有点像这样.... ... 好了,一切都好极了,但那仅仅解释了堆栈溢出。那么疯狂的循环是怎么回事呢? 好...想象一下有这样一个函数(在-->处有有一个断点) void MyRecursiveFunction(){ 当你第一次停在断点处,i的值应该是0,callstack看起来是这样的... MyRecursiveFunction() 现在调用MyRecrusive函数,每一次调用这个函数自己,会再一次出现 i=0(虽然我们并不真的在同一个loop里)。若调用MyRecrusive这个函数几个来回,并用实际执行的代码代替之,它将执行类似如下的代码: for(int i=0; i<5; i++){ ... 在visual studio中查看它,看起来总是运行同样的循环,且并不改变变量i的值。暂时,你对此不会有深层次的理解,直到你真正看到堆栈调用。 假如我们看一下callstack, callstack现在看起来是这样的... MyRecursiveFunction() 因此最初想法的结论是,我们无疑地要看看某些递归...但在哪呢?例子中的代码 myRoot.ChildNodes.Add(new SiteMapNode(this, i.ToString(), i.ToString(), i.ToString())); 看起来并不是那么复杂... 在这儿最可疑的是new SiteMapNode() 和myRoot.ChildNodes.Add() ,假如我们用reflector查看一下,那么这将不再那么神秘。 调试问题 最后:) 少一点口舌,多来一点windbg行动... 因它易重新呈现,所以我会在我的机器上重新呈现它,我只要将windbg (File / Attach to process)附到w3wp.exe上,点击g开始即可。然后会重新产生了这个问题,程序中止时提示我这是一个堆栈溢出(我们已经知道了)。 (7e4.ddc): Stack overflow - code c00000fd (first chance) 我们查看一下IC交易网堆栈,使用 !clrstack命令看看是怎么中止的,但我们只能看到.... 0:016> !clrstack ... 这对我们并没有太大的帮助。有时当我们遇到堆栈溢出时,使用!clrstack 命令就会出现一些这样的问题。因此我们还需要使用!dumpstack命令查看一下raw stack。 0:016> !dumpstack 好了,这看起来问题出自ChildNodes属性。使用该属性时,会调用GetChildNodes 函数,这个函数会再次调用BuildSiteMap 函数,从而它又调用了ChildNodes 属性,如此一直下去,导致了堆栈溢出。 结论 在关于BuildSitemap的文档中,你能找到如下段落: BuildSiteMap 方法由 FindSiteMapNode、GetChildNodes和GetParentNode方法的默认实现调用。如果在派生类中重写 BuildSiteMap 方法,请确保它仅加载一次站点地图数据,并在后续调用中返回。 为了避免出现递归和堆栈溢出,最好避免调用该方法,像在BuildSiteMap例子里,我们可以用AddNode方法来添加子节点。 这在归档在Site Map Providers这篇文章中,该文同样值得一读。 BuildSiteMap 一般不应当调用其他的site map提供的方法或属性,因为许多方法和属性默认会实现BuildSiteMap调用。例如,BuildSiteMap中的RootNode会引起递归,从而使之以堆栈溢出而终止。
|
请发表评论