3

我在遗留应用程序(不是我自己编写的)中遇到了一个奇怪的错误,当我更改日历上的日期时,我遇到了 StackOverflow 异常。

简化版本如下。这是包含两个控件的 Windows 窗体的代码隐藏,一个名为label2的标签和一个名为 MonthCalendar 的日历,名为monthCalendar1

我认为这里的想法是创造打字机效果。我在 XP 上,我在 Windows 7 上的同事能够正常运行:

private void monthCalendar1_DateChanged(object sender, DateRangeEventArgs e)
{
    const string sTextDisplay = "Press Generate button to build *** Reports ... ";

    for (var i = 0; i < 45; i++)
    {
        label2.Text = Mid(sTextDisplay, 1, i);
        System.Threading.Thread.Sleep(50);

        //Error on this line
        //An unhandled exception of type 'System.StackOverflowException' occurred in System.Windows.Forms.dll
        Application.DoEvents();
    }
}

public static string Mid(string s, int a, int b)
{
    var temp = s.Substring(a - 1, b);
    return temp;
}

我看不到堆栈跟踪,我只看到:

{无法计算表达式,因为当前线程处于堆栈溢出状态。}

另外,我对询问为什么我没有检查 StackOverflow 异常的堆栈跟踪的评论很感兴趣,因为至少如果没有第三方工具,这似乎是不可能的。

这可能是什么原因造成的?谢谢

4

3 回答 3

9

请记住,程序是基于堆栈的。当您的程序运行时,每个函数调用都会在堆栈上放置一个新条目。每次一个函数完成时,你从堆栈中弹出,看看回到哪里,这样你就可以继续之前的方法。当函数完成且堆栈为空时,程序结束。

重要的是要记住程序堆栈是慷慨的,但有限的。在堆栈空间不足之前,您只能将这么多的函数调用放在堆栈上。当我们说堆栈溢出时,就会发生这种情况。

DoEvents()只是另一个函数调用。您可以将它放在一个长时间运行的任务中,以允许您的程序处理来自操作系统的有关用户活动的消息:例如点击、击键等。它还允许您的程序处理来自操作系统的消息,例如,如果程序需要重新绘制它的窗口。

通常,只有一两条(甚至零条)消息在等待DoEvents()呼叫。你的程序处理这些,DoEvents()调用从堆栈中弹出,原始代码继续。有时,可能有许多消息在等待。如果这些消息中的任何一条也导致再次调用 的代码运行,那么DoEvents()我们现在在调用堆栈中又深了一层。如果该代码反过来发现一条消息等待导致DoEvents()运行,我们将更深一层。也许你可以看到这是怎么回事。

DoEvents(),与MouseMove事件结合使用,是此类问题的常见来源。MouseMove事件会很快堆积在你身上。这也可能发生在KeyPress事件中,当您按下某个键时。

通常,我不认为日历DateChanged事件会出现这种问题,但如果您有DoEvents()其他地方,或者驱动另一个事件(可能在您的标签上)反过来更新您的日历,您可以轻松创建一个循环来强制你的程序陷入堆栈溢出的情况。

您想要做的是探索BackgroundWorder组件,或者更新的Taskasync模式。

您可能还想阅读我关于DoEvents()这个问题的文章:

如何在不“邪恶”的情况下使用 DoEvents()?

于 2013-11-15T15:11:46.687 回答
1

通常,您有一个非常接近堆栈顶部的消息泵。添加大量消息永远不会导致“深”堆栈,因为它们都由顶级泵处理。使用DoEvents是在堆栈的更深处创建一个新的消息泵。如果您正在抽取的消息之一也调用DoEvents,那么您现在在堆栈中拥有更深的消息泵。如果该消息泵有另一条消息调用DoEvents...并且您明白了。

再次清理堆栈的唯一方法是让消息队列为空,此时您开始回调堆栈,直到到达顶层消息泵。

这里的问题是您的代码并不容易。它在一个循环中调用DoEvents很多,因此它需要有一个空闲队列相当长的时间才能真正退出该循环。最重要的是,如果您碰巧有一个“活动”应用程序正在向消息队列发送大量消息,可能有很多monthCalendar1_DateChanged事件,甚至DoEvents是循环使用的其他事件,或者只是其他事件以防止队列为空,并不难相信你的堆栈会变得足够深以产生 SOE。

理想的解决方案当然是不使用DoEvents. 改为编写异步代码,以便您的堆栈深度永远不会超过一个常数值。

于 2013-11-15T15:15:12.860 回答
-3

DoEvents 在任何情况下都不应该使用,并且您不需要子字符串来归档 TypeWriting 效果

这是我目前知道的最好的方法:

    using System.Threading;



    private string text = "this is my test string";
    private void button1_Click(object sender, EventArgs e)
    {
        new Thread(loop).Start();

    }

    private void loop()
    {
        for (int i = 0; i < text.Length; i++)
        {
            AddChar(text[i]);
            Thread.Sleep(50);
        }
    }

    private void AddChar(char c)
    {
        if (label1.InvokeRequired)
            Invoke((MethodInvoker)delegate { AddChar(c); });
        else
            label1.Text += c;
    }
于 2013-11-15T15:15:45.347 回答