【问题标题】:Why does putting DoEvents in a loop cause a StackOverflow exception?为什么将 DoEvents 置于循环中会导致 StackOverflow 异常?
【发布时间】:2013-11-15 14:58:36
【问题描述】:

我在旧应用程序(不是我自己编写的)中遇到了一个奇怪的错误,当我更改日历上的日期时,我遇到了 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;
}

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

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

另外,我对 cme​​ts 很感兴趣,他们问我为什么没有检查 StackOverflow 异常的堆栈跟踪,因为它看起来像 this isn't possible,至少没有第三方工具。

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

【问题讨论】:

  • 可能是 Application.DoEvents();触发monthCalendar1_DateChanged(...)?
  • 看看调试器...堆栈是什么样的?这会很明显
  • 啊,没有什么比对别人的混淆编程投一些反对票更痛苦的了::sympathy::::
  • @P.Brian.Mackey 是的,尽管在不查看堆栈的情况下询问有关 StackOverflowException 的问题显然是有问题的(在任何人跳到我面前之前 - 我没有投反对票;))跨度>
  • XP 版本的 MonthCalendar 相当古怪,在 Vista 中得到了很大改进。并不是说你不能让这段代码表现得不好,只是依靠光标键来获得任意递归。请改用计时器。

标签: c# winforms stack-overflow doevents


【解决方案1】:

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

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

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

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

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

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

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

您可能还想阅读我在DoEvents() 上针对这个问题撰写的文章:

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

【讨论】:

    【解决方案2】:

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

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

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

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

    【讨论】:

      【解决方案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;
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-11-24
        • 1970-01-01
        • 1970-01-01
        • 2017-04-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多