在递归调用之前,是否有方法检查可用堆栈大小?(C#)

在递归调用之前,是否有方法检查可用堆栈大小?(C#),c#,recursion,stack,artificial-intelligence,stack-overflow,C#,Recursion,Stack,Artificial Intelligence,Stack Overflow,对于C#AI程序,我使用递归调用来查找最佳下一步(使用30x30数组存储当前板状态)。对于我所做的每一个动作,我想看看我能在新的董事会状态下做出哪些可能的动作是最好的。。。以此类推,直到我到达“游戏结束”位置(在该状态下不可能再移动),或者计时器停止进程,不再进行递归调用(并返回“最”已知位置)。这只是为了解释为什么我必须使用递归(不是尾部递归),我不能使用单个(全局)板状态,但必须从当前状态搜索所有可能的板状态 (有时)我得到一个System.StackOverflowException。在下

对于C#AI程序,我使用递归调用来查找最佳下一步(使用30x30数组存储当前板状态)。对于我所做的每一个动作,我想看看我能在新的董事会状态下做出哪些可能的动作是最好的。。。以此类推,直到我到达“游戏结束”位置(在该状态下不可能再移动),或者计时器停止进程,不再进行递归调用(并返回“最”已知位置)。这只是为了解释为什么我必须使用递归(不是尾部递归),我不能使用单个(全局)板状态,但必须从当前状态搜索所有可能的板状态

(有时)我得到一个System.StackOverflowException。在下一次递归调用之前,是否有方法检查可用堆栈空间?然后我可以返回当前状态作为“到目前为止找到的最佳位置”,而不进行下一次递归调用。也就是说,当可用堆栈变得太小时,它也应算作基本情况


当然,另一种选择可能是将每个递归调用放在try..catch块中,并将其作为基本情况处理System.StackOverflowException?

实际上,如果现有堆栈上的空间不足,系统将动态扩展堆栈大小。因此,即使您可以测试堆栈的大小,这也不重要

细节

系统根据需要从保留堆栈内存提交额外的页,直到堆栈达到保留大小减去一页(用作防止堆栈溢出的保护页)或系统内存不足,导致操作失败”

也就是说,在递归发生之前,堆栈是一个大小;如果递归导致堆栈溢出,那么发生时堆栈是一个新大小


由于您无法捕获
StackOverflowException
,因此可以使用尾部递归来代替终端递归。以下链接提供了有关将终端递归转换为尾部递归的一些详细信息:

您可以使用队列+循环(
队列
+
while(queue.MoveNext())
)而不是递归和限制队列的大小

或者,您可以对方法的打开调用进行计数,并以这种方式限制递归。
(计算入口和出口,如果入口-存在>maxOpenCalls,则不输入递归)。

从.NET 2开始,您无法捕获


确定堆栈中有多少已被使用的唯一方法是使用我强烈建议不要使用的不安全代码…最好使用显式的基于堆的
堆栈
如果您真的想走这条路,可以使用该方法

正如其他人指出的,从.NET 2.0开始,您无法捕获
StackOverflowException
,但是,从MSDN文档中,您知道前面的方法具有以下行为:

确保剩余的堆栈空间足够大,可以执行 NET框架函数的平均值


根据此方法,当堆栈不够大时,它将抛出一个异常,您可以捕获该异常。实际上,您可以捕获Stackoverflow执行选项,当然递归方法必须执行一些操作 您可以创建如下方法:

void Zoo()
    {
        RuntimeHelpers.EnsureSufficientExecutionStack();
        int[] baba = new int[1024 * 5];
        Zoo();
    }
那就这样说吧

 try
        {
            Zoo();
        }
        //catch (Exception ex)
        catch(InsufficientExecutionStackException ex)
        {
            ex.ProcessException().Show("Good God what are you doing");
        }
这就是流程异常方法的工作原理

public static class Helper{

[System.Runtime.InteropServices.DllImport("kernel32.dll")]
    public static extern uint GetCurrentThreadId();

public static string ProcessException(this Exception ex)
    {
        StringBuilder strBuild = new StringBuilder(5000);
        if (ex is InsufficientExecutionStackException)
        {
            strBuild.AppendLine("#%#%#%#%#% We Ran out of Stack Space on thread id : " + GetCurrentThreadId().ToString() + " @ :" + DateTime.Now.ToString() + " #%#%#%#%#%");
            strBuild.AppendLine(ex.Message);
            string[] ribals = ex.StackTrace.Split('\n');
            strBuild.AppendLine(String.Join("\n", ribals.Take(3).ToArray()));
            strBuild.AppendLine("\nLike this you can have many more lines ...\n");
            strBuild.AppendLine("Main issue  found here :\n" + ribals.Last());
            strBuild.AppendLine("#%#%#%#%#% We Ran out of Stack Space on thread id : " + GetCurrentThreadId().ToString() + " @ :" + DateTime.Now.ToString() + " #%#%#%#%#%");
            return strBuild.ToString();
        }
        Exception inner = ex;
        Enumerable.Range(0, 30).All(x =>
        {
            if (x == 0) strBuild.Append("########## Exception begin on thread id : " + GetCurrentThreadId().ToString() + " @ :" + DateTime.Now.ToString() + " ##########\n");
            strBuild.Append("---------------------[" + x.ToString() + "]---------------------\n");
            strBuild.Append("Message : " + inner.Message + "\nStack Trace : " + inner.StackTrace + "\n");
            strBuild.Append("---------------------[" + x.ToString() + "]---------------------\n");
            inner = inner.InnerException;
            if (inner == null)
            {
                strBuild.Append("########## Exception End on thread id : " + GetCurrentThreadId().ToString() + " @ :" + DateTime.Now.ToString() + " ##########\n\n");
                return false;
            }
            return true;
        });
        return strBuild.ToString();
    }
}

“(有时)我得到一个System.StackOverflowException。”(可能有助于解释这一点。)@DavidO,这并不是说,尽管在递归过程中增加堆栈,但扩展堆栈的内存不足。这取决于当前板的位置。如果足够早地找到答案(基本情况发现得早),我通常不会遇到问题。重新设计您的代码?stackoverflow是错误或坏(C#)代码的标志。您需要大量递归调用来触发stackoverflow。如果您真的想这样做,请使用支持尾部调用的函数式语言,如F#。C#不是为它而设计的。”如果调用递归方法或计划使用大量堆栈空间,则必须使用RuntimeHelpers.ExecuteCodeWithGuarantedCleanup方法。“--您尝试过吗?”确保剩余的堆栈空间足够大,可以执行average.NET Framework函数“。在OPs的情况下,永远不会有足够的空间--这个方法如何知道?每个递归级别都必须重新检查,不是吗?这部分来自MSDN文档,我应该为它添加一个引号,我将更新答案。”。正如DavidO所指出的,为了成为一个有效的解决方法,每个步骤都需要进行检查。@DavidO嗯,我想这样就可以了。但是调用
RuntimeHelpers。在每次调用递归方法时确保有效执行?糟糕,我只能想象你会受到的性能冲击……如果性能是一个问题,递归可能不会是选择的工具。无论如何,我认为如果代码不能被重构为迭代方法,或者如果递归不能在早期阶段被控制,那么这就是答案。