没有递归或无限循环的StackOverflowException?

8

背景

我正在使用一个 DataGridView 控件,并在其中添加了下面的处理程序到 DataGridView.CellFormatting 事件,以便使一些单元格中的值更易于人类阅读。这个事件处理程序一直很好地工作,格式化所有值没有问题。

然而,最近我发现一个非常罕见的情况会导致一个不寻常的错误。我的 DataGridView 中的列用于显示项目的到期日期,它始终具有一个 int 值。 0 表示事件永远不会到期,任何其他值都是到期日期的 UTC 时间戳。对应的 MySQL 数据库列不允许为空。当用户从一个具有到期日期的 DataGridView 行移动到另一个具有到期日期的 DataGridView 行(此时一切仍然正常),然后按下重新加载数据的按钮(不发送更新,实质上调用 DataAdapter.Fill()),程序会生成一个 StackOverflowException**。

没有递归吗?

对我来说,非常不寻常的是,我看不到递归或无限循环的地方。我添加了一个名为 cellFormatCallCount 的类成员,并在每次调用时递增它,但在抛出异常的时候,调试器显示该 int 的值为 1,这是我所期望的,因为我并没有意识到发生递归。

有人能帮助我吗?

如何查看堆栈跟踪?在 VS2008 中,它显示: {Cannot evaluate expression because the current thread is in a stack overflow state.}

最好的问候,

Robinson

private int cellFormatCallCount = 0;
private void myDataGridView_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)  {
    try {
        // to format the cell, we will need to know its value and which column its in
        string value = "";
        string column = "";
        
        // the event is sometimes called where the value property is null
        if (e.Value != null) {
            cellFormatCallCount++; // here is my test for recursion, this class member will increment each call

            // This is the line that throws the StackOverflowException
            /* ********************* */
            value = e.Value.ToString();
            /* ********************* */

            column = actionsDataGridView.Columns[e.ColumnIndex].Name;
        } else {
            return; // null values cannont be formatted, so return
        }

        if (column == "id") {
            // different code for formatting each column
        } else if (column == "title") {
            // ...
        } else {
            // ...
        }
    } finally {
        cellFormatCallCount = 0; // after we are done with the formatting, reset our recursion counter
    }
}

我以为这个帖子是关于这个网站的。哎呀,我需要休息一下 =p - JoshJordan
@JoshJordan - 我也是!我一直在想他是如何从网站上得到一个异常的。唉! - Nick DeVore
如果您在有问题的代码行上设置断点,您能否进入 e.Value 的属性访问?您能够快速查看 e.Value 吗?如果可以,您看到了什么? - JP Alioto
这可能是一个冒险,但 e.Value 里面是什么?如果在那个值上的 ToString 定义了递归操作,你可能会遇到堆栈溢出的问题。听起来很疯狂,但实际上比你想象的更容易发生,特别是当你有一个属性允许你使用类型为 Object 的“set”操作,并且该属性刚好包含自身时。 - Brad Barker
尝试使用CStr(e.Value)会提供任何线索吗? - rvarcher
显示剩余3条评论
7个回答

5

显然,e.Value.ToString() 会再次触发 CellFormatting 事件。这似乎是有道理的。通过调试器很容易找出原因。

但实际上,递归可能是由其他地方引起的,比如你省略的按列格式化。

你的递归检查不可靠,因为 Value==null 也会重置它,并且它似乎被所有列共享。让它更紧密地包围 e.Value.ToString():

if (e.Value != null) 
{
   cellFormatCallCount++; 
   System.Diagnostics.Debug.Assert(cellFormatCallCount <= 1, "Recursion");
   value = e.Value.ToString();
   cellFormatCallCount--; 
   ...
} 

2

完全随机的猜测(没有堆栈跟踪,这是我能做的全部)...

您是否尝试显示/格式化具有潜在递归ToString()的类型?

public string ToString()
{
   return ... this.ToString() ...
   // or
   return String.Format("The value is {0}", this);
}

像这样的错字/错误可能会导致StackOverflowException...


请参见:https://dev59.com/MnVD5IYBdhLWcg3wKoaH#62195 - C. K. Young
如果你想查看堆栈溢出,你需要在它发生的地方设置断点,而不是在异常处。我建议将断点设置在显示 SO 的那一行,并尝试查看程序从哪里开始执行。 - Darren Kopp
不,我没有对DataGridViewCellFormattingEventArgs进行子类化。 - gnirts
1
如果您在导致堆栈溢出的代码行上设置一个(可能是有条件的)断点,您应该能够进入它...这可能会帮助您识别递归路径... - Daniel LeCheminant
你可以通过在方法中先放置一个等效的 e.Value.ToString(),并查看是否会出现stackoverflow来测试ToString()方法的负值。如果没有出现,则不能是ToString的问题。我认为,如果.net提供的DataGridViewCellFormattingEventArgs类以这种方式实现ToString,那么这将是奇怪的 :) - asgerhallas
噢,Brad Barker在问题的评论中说了同样的话...抱歉重复了。 - asgerhallas

1

@Daniel:如果那是问题,那么它不会在这一行引发异常吗?

if (e.Value != null) {

@gnirts: 你能同时发布完整的方法和堆栈跟踪吗?

@BCS(下面):我认为可能是这样,但它很容易在未显示在deo中的某些代码中。

PS. 对不起,这应该是一条评论,但我没有足够的声望:-D


哦,太好了!谢谢 :-D - asgerhallas
你是100%正确的,之前的那行代码会引发异常;] - Daniel LeCheminant

1

考虑到这是一个事件,它可能会触发自身吗?


我认为它没有自动触发,因为计数器会增加,对吗?MSDN页面说每次需要重新绘制表单时都会触发该事件——这非常频繁,这使得在此代码块中使用断点非常耗时。我仍在寻找一种好的调试方法。 - gnirts
但是你没有在方法内部强制重绘它,使用一些未显示的代码?另外,你尝试过不使用try/finally块吗?以防它在你不知道的情况下将其设置为0。 - asgerhallas

1
尝试将cellFormatCallCount变量设为静态(static)变量,这样它就能被该类的所有实例共享。我怀疑事件(event)在某种程度上会触发自身,但你可能没有看到它,因为cellFormatCallCount仅局限于处理事件的每个类实例,并且从未增加超过1。如果是这种情况,那么导致堆栈溢出(递归)的实际触发点可能在方法中的任何位置,只是在该行运行时恰好用完了堆栈空间。
一旦您将变量设为静态(static),您可以在它超过某个(小)值(例如2)时抛出异常。这个异常应该会留下一个可查看的堆栈跟踪。

我在类成员中添加了static修饰符,但在发生堆栈溢出时计数仍为1。 - gnirts

0

我刚遇到了一个类似的stackoverflow问题,但没有任何详细信息。

我的问题是由于实例化了一个不应该被实例化的对象。我删除了这个有问题的新对象,一切都好了。

在上述情况下,如果没有看到更多的代码,请确保不要使用“=-”触发多个事件,以相应地取消这些事件。

我希望这可以帮助到某些人。


0

这与问题无关,但您实际上可以在完全没有递归的情况下出现 StackOverflowException

throw new StackOverflowException();

请查看:https://dev59.com/MnVD5IYBdhLWcg3wKoaH#62244 :-) - C. K. Young
这个会有一个堆栈跟踪。真正的堆栈溢出不会让你评估任何东西。 - Darren Kopp
@Darren Kopp:一个“真正的”StackOverflow网站可以让你提问、回答和评论问题 :) - Mehrdad Afshari

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接