为什么将DoEvents放入循环会导致StackOverflow异常?

时间:2013-11-15 14:58:37

标签: c# winforms stack-overflow doevents

我在遗留应用程序中遇到了一个奇怪的错误(不是我自己编写的),当我在日历上更改日期时,我收到了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异常的堆栈跟踪,因为它看起来像this isn't possible至少没有第三方工具。

可能导致这种情况的原因是什么?感谢

3 个答案:

答案 0 :(得分:6)

请记住,程序是基于堆栈的。当程序运行时,每次调用函数时,都会在堆栈上放置一个新条目。每次函数完成时,都会从堆栈弹出以查看返回的位置,因此您可以继续使用先前的方法。当函数完成且堆栈为空时,程序结束。

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

DoEvents()只是另一个函数调用。您可以将它放在一个长期运行的任务中,以允许您的程序处理来自操作系统的有关用户活动的消息:点击,击键等。它还允许您的程序处理来自操作系统的程序所需的消息。重新画出它的窗户。通常,只有一个或两个(甚至零)消息等待DoEvents()调用。您的程序处理这些,DoEvents()调用从堆栈弹出,原始代码继续。有时候,可能会有很多消息在等待。如果这些消息的任何也会导致代码再次调用DoEvents(),那么我们现在处于调用堆栈的另一个深层。如果该代码反过来发现一条等待导致DoEvents()运行的消息,那么我们将又是另一个层次。你可以看到它的发展方向。

与MouseMove事件结合使用的

DoEvents()是这类问题的常见原因。 MouseMove事件可以很快堆积在你身上。当您有一个按下的键时,KeyPress事件也会发生这种情况。通常情况下,我不希望Calendar DateChanged事件出现这种问题,但如果您在其他地方有DoEvents,或者驱动另一个事件(可能在您的标签上)反过来更新您的日历,您可以轻松创建一个周期将强制您的程序螺旋式进入StackOverflow情况。

您要做的是探索 BackgroundWorder 组件。

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

  

How to use DoEvents() without being "evil"?

答案 1 :(得分:1)

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

堆栈再次清除的唯一方法是将消息队列清空,此时您开始调用堆栈,直到到达顶级消息泵。

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

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

答案 2 :(得分:-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;
    }