让我们把它想象成一个家谱,一个父亲有孩子,孩子有孩子,孩子有孩子等等...... 所以我有一个递归函数,让父亲使用Recursion来获取子项,现在只需将它们打印到调试输出窗口......但是在某些时候(让它运行并打印26000行后一小时)它给了我StackOverFlowException。
所以我真的没有内存了吗?嗯?那我不应该得到一个“内存不足异常”吗?在其他帖子上我发现有人说如果递归调用的次数太多,你可能仍会得到一个SOF异常......
无论如何,我的第一个想法就是把树打破成更小的子线。所以我知道我的根父亲总是有这五个孩子的事实,所以不要用root调用我的方法一次传递给它,我说好吧用它传给它五次与它通过它...它帮助我思考..但它们中的一个仍然是如此之大 - 当它崩溃时26000行 - 并且仍然有这个问题。
应用程序域和在某个特定深度的运行时创建新进程如何?这有帮助吗?
如何创建自己的堆栈并使用它而不是递归方法?这有帮助吗?
这里也是我的高级代码,请看一下,也许实际上有一些愚蠢的错误会导致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);
}
}
答案 0 :(得分:10)
如何创建自己的堆栈并使用它而不是递归方法?这有帮助吗?
是!
当您实例化Stack<T>
时,它将存在于堆上并且可以任意增长(直到用完可寻址内存)。
如果使用递归,则使用调用堆栈。调用堆栈比堆小得多。默认值为每个线程1 MB的调用堆栈空间。请注意,这可以更改,但不建议这样做。
答案 1 :(得分:4)
StackOverflowException与OutOfMemoryException完全不同。
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)
,在第二个中忽略了++i
- 每个中的等效错误,它会导致第一个中的SOE和一个永远不会停止的程序第二
对于您正在讨论的代码类型,您可以根据以前的结果继续生成越来越多的结果,您可以将结果放在数据结构中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<string> seen = new HashSet<string>();
来避免对已经处理过的项进行排队。
编辑:更好地添加如何使用HashSet来捕获重复项。首先设置一个HashSet,这可能只是:
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.
或者,如果重复的字符串有效,我们只想跳过执行第二次调用:
{{1}}
答案 2 :(得分:2)
我认为你有一个递归的环(无限递归),而不是真正的堆栈溢出错误。如果你有更多的内存用于堆栈 - 你也会得到溢出错误。
进行测试:
声明用于存储可操作对象的全局变量:
private Dictionary<int,object> _operableIds = new Dictionary<int,object>();
...
private void Start()
{
_operableIds.Clear();
Recurtion(start_id);
}
...
private void Recurtion(int object_id)
{
if(_operableIds.ContainsKey(object_id))
throw new Exception("Have a ring!");
else
_operableIds.Add(object_id, null/*or object*/);
...
Recurtion(other_id)
...
_operableIds.Remove(object_id);
}