3

I was getting a weird error in a legacy application (not written by myself), where I was getting a StackOverflow exception when I changed the date on a calendar.

A simplified version is below. This is the code-behind of a Windows Form containing two controls, a Label called label2 and a calendar called MonthCalendar called monthCalendar1.

I think the idea here was to create a typewriter effect. I am on XP, my colleague on Windows 7 is able to run this ok:

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;
}

I can't see the stack trace, all I see is:

{Cannot evaluate expression because the current thread is in a stack overflow state.}

Also, I'm interested in the comments asking why I haven't checked the stack trace of my StackOverflow exception, as it looks like this isn't possible without third party tools at least.

What could be causing this? Thanks

Community
  • 1
  • 1
JMK
  • 27,273
  • 52
  • 163
  • 280
  • why do you need to call `DoEvents`? – Daniel A. White Nov 15 '13 at 15:00
  • Share the exception and what line it's occurring on please. – Tyler Lee Nov 15 '13 at 15:00
  • @DanielA.White There's no good reason, and I'm changing the code, I'm just wondering what causes the error, only on some computers. – JMK Nov 15 '13 at 15:00
  • I don't know why you have a problem, BUT, putting Application.DoEvents there is very dangerous, it should only be used in case you have a thread that may be blocking the whole UI, but not in such an event. – Rafa Nov 15 '13 at 15:01
  • Replace with `label2.Refresh();` – Rotem Nov 15 '13 at 15:02
  • 3
    Maybe the Application.DoEvents(); triggers the monthCalendar1_DateChanged(...)? – flayn Nov 15 '13 at 15:02
  • 4
    look at the debugger... what does the stack look like? that'll make it pretty obvious – Robert Levy Nov 15 '13 at 15:02
  • 3
    Ah there's nothing more painful than landing a few downvotes against someone else's obfuscated programming :::sympathy:::: – P.Brian.Mackey Nov 15 '13 at 15:04
  • @P.Brian.Mackey ha yeah, I thought I made it pretty clear that I noticed an odd bug in legacy code I didn't write, and was curious as to what causes it but ah well! – JMK Nov 15 '13 at 15:05
  • 1
    @P.Brian.Mackey true, though asking a question about a StackOverflowException apparently without looking at the stack is questionable (and before any one jumps at me - I didn't downvote ;)) – decPL Nov 15 '13 at 15:06
  • I can't see the stack trace, updated the question. – JMK Nov 15 '13 at 15:11
  • 1
    The XP version of MonthCalendar is pretty cranky, much improved in Vista. Not that you cannot get this code to behave badly, just lean on a cursor key to get arbitrary recursion. Use a timer instead. – Hans Passant Nov 15 '13 at 15:40

3 Answers3

9

Remember, programs are stack-based. As your program runs, every function call places a new entry on the stack. Every time a function completes, you pop from the stack to see where to go back to, so you can continue the prior method. When a function completes and the stack is empty, the program ends.

It's important to remember the program stack is generous, but finite. You can only put so many function calls on the stack before it runs out of space. This is what happens when we say the stack overflows.

DoEvents() is just another function call. You might put it in a long-running task to allow your program to handle messages from the operating system about user activity: things like clicks, keystrokes, etc. It also allows your program to handle messages from the operating system, for example if the program needs to re-draw it's windows.

Normally, there will only be one or two (or even zero) messages waiting for a DoEvents() call. Your program handles these, the DoEvents() call is popped off the stack, and the original code continues. Sometimes, there may be many messages waiting. If any of those messages also results in code running that again calls to DoEvents(), we are now another level deep in the call stack. And if that code in turn finds a message waiting which causes DoEvents() to run, we'll be yet another level deep. Maybe you can see where this is going.

DoEvents(), used in conjuction with the MouseMove event, is a common source of problems like this. MouseMove events can pile up on you very quickly. This can also happen with KeyPress events, when you have a key that is held down.

Normally, I wouldn't expect a Calendar DateChanged event to have this kind of problem, but if you have DoEvents() somewhere else, or drive another event (perhaps on your label) that in turn updates your calendar, you can easily create a cycle that will force your program to spiral into a stack overflow situation.

What you want to do instead is explore the BackgroundWorder component, or the newer Task and async patterns.

You may also want to read my write-up on DoEvents() for this question:

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

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
1

Normally you have a message pump pretty close to the top of the stack. Adding lots of messages isn't ever resulting in a "deep" stack, as they are all processed by a top level pump. Using DoEvents is creating a new message pump at a point deeper in the stack. If one of the messages that you are pumping also calls DoEvents, you now have a message pump even deeper on in the stack. If that message pump has another message that calls DoEvents ... and you get the idea.

The only way for the stack to clear up again is for the message queue to be empty, at which point you start calling back up the stack until you get to the top level message pump.

The problem here is that your code doesn't make it easy. It calls DoEvents a lot in a loop, so it needs to have an idle queue for quite some time to actually get back out of that loop. On top of that, if you happen to have an "active" application that's sending lots of messages to the message queue, possibly lots of monthCalendar1_DateChanged events, or even other events using DoEvents in a loop, or just other events to keep the queue from being empty, it's not particularly hard to believe that your stack would get deep enough to result in an SOE.

The ideal solution of course is to not use DoEvents. Write asynchronous code instead, so that your stack depth never exceeds a constant value.

Servy
  • 202,030
  • 26
  • 332
  • 449
-3

DoEvents shouldn't use in any case and you don't require substring to archive a TypeWriting effect

Here is the best way I know at the moment:

    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;
    }