0

I am new to C# and events/delegates. I have a test app that I created that mimics what I am trying to do in a larger project. I continue to get an unhandled StackOverflowException when running this and I can quite figure out why. I've tried changing the BeginInvok to Invoke but that just seems to cause the app to completely hang up. Do I not have this set up correctly?

Here is the windows forms code:

public partial class Form1 : Form
{

    delegate void AddProcessingEventItemCallback(string eventMessage);

    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        TestClass t = new TestClass();
        t.OnJobTaskStatusUpdate += new TestClass.JobTaskStatusUpdate(JobTaskStatusUpdateHandler);

        Thread ts = new Thread(new ThreadStart(t.RunTest));
        ts.Start();
        ts.Join();
    }

    private void JobTaskStatusUpdateHandler(object sender, string statusMessage)
    {
        AddProcessingEventItem(statusMessage);
        Application.DoEvents();
    }

    private void AddProcessingEventItem(string eventMessage)
    {
        if (this.listBox1.InvokeRequired)
        {
            AddProcessingEventItemCallback d = new AddProcessingEventItemCallback(AddProcessingEventItem);
            this.BeginInvoke(d, new object[] { eventMessage });
        }
        else
        {
            listBox1.SelectedIndex = listBox1.Items.Add(eventMessage);
            Application.DoEvents();
        }
    }
}

and here is the code in the class:

public class TestClass
{

    public delegate void JobTaskStatusUpdate(object sender, string statusMessage);
    public event JobTaskStatusUpdate OnJobTaskStatusUpdate;

    public void RunTest()
    {

        for (int i = 1; i <= 500; i++)
        {
            UpdateJobTaskStatus(i.ToString(), true);
            //System.Threading.Thread.Sleep(1000);
        }
    }

    internal void UpdateJobTaskStatus(string statusMessage, bool addStatusTime)
    {
        UpdateJobTaskStatus(statusMessage, addStatusTime, false);
    }

    internal void UpdateJobTaskStatus(string statusMessage, bool addStatusTime, bool addLineSpacer)
    {
        OnJobTaskStatusUpdate(this, addStatusTime ? string.Format("{0} :\t{1}", DateTime.Now.ToString(), statusMessage) : string.Format("\t\t\t{0}", statusMessage));
        if (addLineSpacer)
            OnJobTaskStatusUpdate(this, "\t\t\t");
    }
}

I've been searching for this for a while now. Any help would be greatly appreciated.

3 Answers3

3

Your problem starts here:

private void button1_Click(object sender, EventArgs e)
{
    ...
    Thread ts = new Thread(new ThreadStart(t.RunTest));
    ts.Start();
    ts.Join();   // blocks the main thread
}

The Join() is parking your main thread, and that causes you to do emergency repairs with DoEvents() all over the place. It is quite possible the StackOverflow comes from too many re-entrant calls to DoEvents(), I'm not sure.

But the better solution seems to be: Use a BackgroundWorker.

H H
  • 263,252
  • 30
  • 330
  • 514
2

You code contains an indirect recursion which is not easy to spot. What happens is something like this:

When you click the button your main thread kicks off a new thread and waits for it to complete. The worker thread will run along and execute the loop in TestClass n times. In each iteration it will enqueue a new AddProcessingEventItemCallback via Control.BeginInvoke.

As the main thread is blocked in ts.Join however it is unable to invoke the delegate. (That's also why your program will block indefinitely when you do an Invoke instead of a BeginInvoke. The worker thread is waiting for the main thread to complete execution of the callback while the main thread is waiting for the worker thread to finish execution.)

Eventually the worker thread will have completed its work and the main thread returns from the call to ts.Join. Now it starts to execute the first of the enqueued callbacks. While it is inside the AddProcessingEventItem method however the main thread calls Application.DoEvents() (second line in the "else" clause) which makes it process the next AddProcessingEventItemCallback delegate. So the main thread recursively (and indirectly) calls AddProcessingEventItem n times (n being the number of loop iterations in your TestClass), enough to blow your stack.

If you just remove the call to Application.DoEvents in the else-clause your program will run without blowing the stack. However as Henk has pointed out, the better solution is not to block your main thread at all and get rid of the Application.DoEvents calls entirely. (Also see Jon's answer here. Especially note the last sentence in his answer.)

Community
  • 1
  • 1
afrischke
  • 3,836
  • 17
  • 30
0

Your problem lies in ts.Join() which blocks your GUI thread. BackgroundWorker is not that necessary but you should avoid Application.DoEvents() and implement your logic in separate thread. If you need any synchronization/waiting use mutex/semaphore, ManualResetEvent and so on.

grzegorz_p
  • 483
  • 1
  • 4
  • 14