2013-11-15 66 views
1

我在遗留应用程序(不是我自己写的)中出现了一个奇怪的错误,当我在日历上更改日期时,出现StackOverflow异常。为什么将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没有第三方工具在最小。

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

+0

为什么你需要调用'DoEvents'? –

+0

请分享例外情况和发生什么。 –

+0

@ DanielA.White没有什么好的理由,我正在更改代码,我只是想知道是什么原因导致了错误,只是在某些计算机上。 – JMK

回答

6

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

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

DoEvents()只是另一个函数调用。你可以把它放在一个长期运行的任务中,让你的程序处理来自操作系统的关于用户活动的消息:点击,击键等等。它还允许你的程序处理程序需要的操作系统消息重新绘制它的窗户。通常,只有一个或两个(甚至零)消息等待DoEvents()调用。你的程序处理这些,DoEvents()调用从堆栈中弹出,并且原始代码继续。有时候,可能有很多消息在等待。如果这些消息的任何也导致代码正在运行,并再次调用DoEvents(),我们现在是调用堆栈中另一个深度级别。如果该代码反过来发现等待导致DoEvents()运行的消息,我们将会进一步深入。你可以看到这是怎么回事。

DoEvents()与MouseMove事件结合使用是这类问题的常见来源。 MouseMove事件可能会非常快速地堆积在您身上。 KeyPress事件也可能发生,当你有一个按下的键。通常情况下,我不希望Calendar DateChanged事件具有此类问题,但如果您在其他位置有DoEvents,或者驱动另一个事件(可能在您的标签上),这会反过来更新您的日历,您可以轻松创建一个循环将强制你的程序进入一个StackOverflow状态。

你想要做的是改为探索BackgroundWorder组件。

您可能还需要阅读我DoEvents()这个问题写了:

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

1

通常你有一个消息泵相当接近堆栈的顶部。添加大量消息不会产生“深度”堆栈,因为它们全部由顶级泵处理。使用DoEvents正在堆栈的更深处创建一个新的消息泵。如果您正在抽取的其中一条消息也称为DoEvents,则您现在在堆栈中有一个消息泵,甚至更深。如果该消息泵有另一个消息,呼叫DoEvents ...并且你明白了。

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

这里的问题是,你的代码并不容易。它在一个循环中调用DoEvents a lot,所以它需要有一段时间的空闲队列才能真正退出该循环。最重要的是,如果您恰好有一个正在向消息队列发送大量消息的“活动”应用程序,可能会有很多事件,或者甚至是在循环中使用DoEvents的其他事件,或者只是其他事件可以阻止队列是空洞的,相信你的筹码足够深入以产生国有企业并不难。

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

-3

的DoEvents不应该在任何情况下使用,你不需要串归档打字效果

这里是我知道此刻的最佳方式:

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; 
    } 
相关问题