有没有办法在递归调用之前检查可用的堆栈大小? (C#)

时间:2012-09-09 15:54:23

标签: c# recursion stack artificial-intelligence stack-overflow

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

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

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

5 个答案:

答案 0 :(得分:2)

实际上,如果现有堆栈上的空间不足,系统将动态扩展堆栈大小。所以,即使你可以测试堆栈的大小,也没关系。

http://msdn.microsoft.com/en-us/library/windows/desktop/ms686774(v=vs.85).aspx详细信息

  

系统会根据需要从保留堆栈内存中提交其他页面,直到堆栈达到保留大小减去一页(用作防护页面以防止堆栈溢出)或系统内存太低操作失败“。

这就是说,在递归发生之前,堆栈是一个大小;如果递归导致堆栈溢出,那么当发生这种情况时,堆栈是一个新的大小。

由于无法捕获StackOverflowException而不是终端递归,因此可以使用尾递归。以下链接提供了有关将终端回归转换为尾部重复的一些详细信息:http://www.thomaslevesque.com/2011/09/02/tail-recursion-in-c/

答案 1 :(得分:2)

您可以使用队列+循环(Queue<TNode> + while (queue.MoveNext()))而不是递归,并限制队列的大小。

或者您可以对该方法计算打开调用并以此方式限制递归。 (计算条目和退出,如果条目存在&gt; maxOpenCalls,则不输入递归)。

答案 2 :(得分:2)

如果您真的想沿着这条路走下去,可以使用EnsureSufficientExecutionstack方法。

正如其他人所指出的那样,从.NET 2.0开始,你无法捕获StackOverflowException,但是,从MSDN文档中你知道上一个方法有以下行为:

  

确保剩余的堆栈空间足够大以执行   平均.NET Framework函数。

根据此方法,当堆栈不够大时,它会抛出可以捕获的InsufficientExecutionStackException异常

答案 3 :(得分:1)

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

确定已经使用了多少堆栈的唯一方法是使用我强烈建议的不安全代码...更好地使用显式的基于堆的Stack<T>

答案 4 :(得分:0)

实际上您可以捕获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();
    }
}