3

Guys I know this question has been asked many times but I still cannot find a single reply that makes sense to me.

I have a form with over 400 controls on it.

I have a background thread that polls a bunch of equipment and gathers all kinds of data to be displayed on the form.

I then call one method "UpdateDisplay(string[] data)". This one routine takes all the information in the string array data[] and fills in all the components on the form. I have Labels, TextBoxes being filled in. Panels and TableLayouts and other controls being shown and hidden.

HUNDREDS of them!!

If I have to test each and every component to see if I have to invoke my program will turn into 5 billion lines of code!!!!!

Is there some way to simply see if the entire UpdateDisplay method needs to be invoked on the UI thread rather then all 400+ components it touches?

I put the following code:

    if (InvokeRequired)
    {
        BeginInvoke(new MethodInvoker(() => UpdateDisplay(data)));
    }

as the first statement in the display method and I no longer get the run time exception about calling ui components from a non-ui thread.

followed by the rest of the update method with hundreds of components being updated with the information in data[]

But now I am getting a bunch of System.InvalidOperationException in System.Forms.dll???

If I set the debug exception option to break on all invalidoperationexceptions I see that they are being thrown randomly when updating components within the UpdateDisplay method with the nastygram about updating components from the non-ui thread.

Can someone please help me understand and fix this?

I can post the entire UpdateDisplay method to show how rediculous it would be if I had to wrap every component update call with an invokerequired if statement. Not exaggerating it would add like three lines of code per control or roughly 1200 lines of additional code!! That is just crazy!

ctbram
  • 65
  • 6
  • "the rest of the update method" - Is this a simple mistake of a missing `return` after that `BeginInvoke`? `BeginInvoke` schedules the delegate for execution on the UI thread, but if you really do have more logic after that `if`, it is still going to be run on the worker thread. – Dark Falcon Nov 11 '15 at 20:56
  • Look at http://stackoverflow.com/a/661706/808901 – Rod Lima Nov 11 '15 at 20:58
  • What is the exception message and stack trace? – Yacoub Massad Nov 11 '15 at 20:59
  • Why are you testing for `InvokeRequired`? Aren't you sure that you are running `UpdateDisplay` on a background thread? – Yacoub Massad Nov 11 '15 at 21:02

3 Answers3

2

This is a sample of how I update a ListBox with messages from another thread.

private void WriteProgressMessage(string message)
{
    if (this.InvokeRequired)
    {
        this.Invoke(new Action(() => this.WriteProgressMessage(message)));
    }
    else
    {
        this.ProgressList.Items.Add(message);
        this.ProgressList.SelectedIndex = this.ProgressList.Items.Count - 1;
        this.ProgressList.SelectedIndex = -1;
    }
}
JimmyV
  • 493
  • 3
  • 12
  • is this different from what I did? I just don't wrap all the remain code after the if (InvokeRequired) inside an else block because it did not seem to be needed – ctbram Nov 11 '15 at 21:01
  • @ctbram: If it weren't needed, you wouldn't be getting the errors. Since you are getting the errors, it is clear it is needed. – Dark Falcon Nov 11 '15 at 21:13
2

You did not provide sufficient code, but if your method looks something like this:

void UpdateDisplay(string[] data)
{
    if (InvokeRequired)
    {
        BeginInvoke(new MethodInvoker(() => UpdateDisplay(data)));
    }
    Label1.Text = data[0];
    // Update more controls here
}

then you are running UpdateDisplay twice, once on the UI thread due to the BeginInvoke and once on the worker thread when BeginInvoke returns. The usual pattern is that if you use Invoke or BeginInvoke to invoke yourself, the method then returns immediately and does no further processing:

void UpdateDisplay(string[] data)
{
    if (InvokeRequired)
    {
        BeginInvoke(new MethodInvoker(() => UpdateDisplay(data)));
        return; // Don't run any code below when BeginInvoke returns
    }
    Label1.Text = data[0];
    // Update more controls here
}

JimmyV also gave an alternate version that also does no further work after the BeginInvoke:

void UpdateDisplay(string[] data)
{
    if (InvokeRequired)
    {
        BeginInvoke(new MethodInvoker(() => UpdateDisplay(data)));
    }
    else // Don't run any code below when BeginInvoke returns
    {
        Label1.Text = data[0];
        // Update more controls here
    }
}
Dark Falcon
  • 43,592
  • 5
  • 83
  • 98
  • Ureka! That appears to be it and I think I am finally wrapping my head around this invoke thing. Part of my problem is I do not yet fuly understand lambda expressions either. But after adding the return; as in the first example to my version the errors have stopped. I also noticed that Dark Falcon also hit on the right issue. I thank you all very much though. As C# and the .NET api keep mutating it gets harder and harder to keep up. – ctbram Nov 11 '15 at 21:49
0

One possibility is that since you are using BeingInvoke, rather than Invoke to update the UI thread there may be an issue of synchronisation. Its hard to tell without seeing a more complete code sample, buy try calling Invoke instead and see if you get less errors

Anduril
  • 1,236
  • 1
  • 9
  • 33