所以我有一个递归函数,让父亲使用递归来获取孩子,现在只需打印它们来调试输出窗口…但是在某个时候(在一个小时之后让它运行和打印就像26000行)给我一个StackOverFlowException.
那么真的用完了内存?嗯?那么我应该不会得到一个“内存不足例外”?在其他帖子中,我发现人们说如果递归电话的数量太多,你可能还会得到一个SOF异常…
无论如何,我的第一个想法是将树分解成更小的sub-strees.我知道一个事实,我的根父亲总是有这五个孩子,所以,而不是调用我的方法一次,根传递给它,我说称它为五次与孩子的根通过它..它帮助我的想法,但仍然有一个是如此之大 – 26000行,当它崩溃 – 仍然有这个问题.
应用程序域和运行时在一定程度上创建新进程如何?这有帮助吗
如何创建我自己的Stack并使用它而不是递归方法?有帮助吗
这里也是我的代码的高级别,请看看,也许实际上有一些愚蠢的错误导致SOF错误:
private void MyLoadMethod(string conceptCKI) { // make some script calls to DB,so that moTargetConceptList2 will have Concept-Relations for the current node. // when this is zero,it means its a leaf. int numberofKids = moTargetConceptList2.ConceptReltns.Count(); if (numberofKids == 0) return; for (int i = 1; i <= numberofKids; i++) { oUCMRConceptReltn = moTargetConceptList2.ConceptReltns.get_ItemByIndex(i,false); //Get the concept linked to the relation concept if (oUCMRConceptReltn.SourceCKI == sConceptCKI) { oConcept = moTargetConceptList2.ItemByKeyConceptCKI(oUCMRConceptReltn.TargetCKI,false); } else { oConcept = moTargetConceptList2.ItemByKeyConceptCKI(oUCMRConceptReltn.SourceCKI,false); } //builder.AppendLine("\t" + oConcept.PrimaryCTerm.SourceString); Debug.WriteLine(oConcept.PrimaryCTerm.SourceString); MyLoadMethod(oConcept.ConceptCKI); } }
解决方法
OOME意味着这个进程根本就没有可用的内存.这可能是在尝试使用新的堆栈创建一个新线程,或尝试在堆上创建一个新对象(还有一些其他情况).
SOE意味着线程的堆栈 – 默认为1M,尽管在线程创建中可以设置不同,或者可执行文件具有不同的默认值;因此ASP.NET线程的默认值为256k,而不是1M.这可能是在调用方法或分配本地时.
当调用一个函数(方法或属性)时,调用的参数放在堆栈上,函数应该返回的地址放在堆栈上,然后执行跳转到调用的函数.然后一些本地人将被放置在堆栈上.随着功能继续执行,可能会放置更多的内容. stackalloc还将显式地使用一些堆栈空间,否则将使用堆分配.
然后它调用另一个功能,同样会发生.然后该函数返回,并且执行跳转回存储的返回地址,并且堆栈内的指针向后移动(无需清除放置在堆栈上的值,它们现在就被忽略),并且该空间可用.
如果你使用这个1M的空间,你会得到一个StackOverflowException.因为1M(甚至256k)是大量的内存用于这些这样的使用(我们不会在堆栈中放置真正的大对象),可能导致SOE的三件事情是:
有人认为使用stackalloc不是最好的方法,而且用完了1M的速度.
>有人认为通过创建一个小于通常的堆栈的线程来优化它是一个好主意,当它不是,并且他们使用那个小堆栈.
>递归(无论是直接还是通过几个步骤)调用都成为无限循环.
>这不是很无限的,但它足够大.
你有4案1和2是非常罕见的(你需要相当刻意冒险).情况3是迄今为止最常见的,并且表示一个错误,因为递归不应该是无限的,而是一个错误意味着它是.
具有讽刺意味的是,在这种情况下,您应该很高兴您采用递归方法而不是迭代 – SOE显示错误及其位置,而采用迭代方法可能会产生一个无限循环,使所有事情都停止,更难找到
现在对于情况4,我们有两个选择.在非常罕见的情况下,我们只有稍微太多的电话,我们可以在一个更大的堆栈的线程上运行它.这不适用于您.
相反,您需要从递归方法转换为迭代方法.大多数时候,这不是很难,以为它可以很好玩.该方法不是再次调用,而是使用循环.例如,考虑经典教学 – 阶乘法的例子:
private static int Fac(int n) { return n <= 1 ? 1 : n * Fac(n - 1); }
而不是使用递归,我们循环使用相同的方法:
private static int Fac(int n) { int ret = 1; for(int i = 1; i <= n,++i) ret *= i; return ret; }
您可以看到为什么这里堆栈空间较小.迭代版本也将在99%的时间内更快.现在,我们假设我们不小心在第一个调用了Fac(n),并且在第二个中忽略了我的第二个 – 相当于每个的错误,并且它导致了第一个国有企业和第二个从不停止的程序.
对于您正在谈论的代码类型,您根据以前的结果继续生成越来越多的结果,您可以将数据结构中的结果(Queue< T>和Stack< T> ;两者都适用于很多情况),所以代码变成如下):
private void MyLoadMethod(string firstConceptCKI) { Queue<string> pendingItems = new Queue<string>(); pendingItems.Enqueue(firstConceptCKI); while(pendingItems.Count != 0) { string conceptCKI = pendingItems.Dequeue(); // make some script calls to DB,so that moTargetConceptList2 will have Concept-Relations for the current node. // when this is zero,it means its a leaf. int numberofKids = moTargetConceptList2.ConceptReltns.Count(); for (int i = 1; i <= numberofKids; i++) { oUCMRConceptReltn = moTargetConceptList2.ConceptReltns.get_ItemByIndex(i,false); //Get the concept linked to the relation concept if (oUCMRConceptReltn.SourceCKI == sConceptCKI) { oConcept = moTargetConceptList2.ItemByKeyConceptCKI(oUCMRConceptReltn.TargetCKI,false); } else { oConcept = moTargetConceptList2.ItemByKeyConceptCKI(oUCMRConceptReltn.SourceCKI,false); } //builder.AppendLine("\t" + oConcept.PrimaryCTerm.SourceString); Debug.WriteLine(oConcept.PrimaryCTerm.SourceString); pendingItems.Enque(oConcept.ConceptCKI); } } }
(我还没有完全检查这个,只是添加排队而不是递归到你的问题的代码).
那么这应该与你的代码大致相同,但是迭代.希望这意味着它会奏效.请注意,如果要检索的数据具有循环,则此代码中可能存在无限循环.在这种情况下,这个代码将会抛出一个异常,当它填满了太多的东西来处理队列.您可以调试源数据,也可以使用HashSet来避免对已经处理的项进行排队.
编辑:更好地添加如何使用HashSet来捕获重复.首先设置一个HashSet,这可能只是:
HashSet<string> seen = new HashSet<string>();
或者如果字符串不区分大小写,那么您最好使用:
HashSet<string> seen = new HashSet<string>(StringComparison.InvariantCultureIgnoreCase) // or StringComparison.CurrentCultureIgnoreCase if that's closer to how the string is used in the rest of the code.
然后在您使用字符串之前(或者可能在您将其添加到队列之前,您具有以下之一:
如果重复的字符串不应该发生:
if(!seen.Add(conceptCKI)) throw new InvalidOperationException("Attempt to use \" + conceptCKI + "\" which was already seen.");
或者如果重复的字符串有效,并且我们只想跳过执行第二个调用:
if(!seen.Add(conceptCKI)) continue;//skip rest of loop,and move on to the next one.