0

In my application, I have a new Windows Form (called Monitor) which consists of basically a big display (which is a custom user control), which itself consists of many small charts (also user controls) arranged in a grid. When I instantiate the monitor form, a new display object is created, which then goes on to create a bunch of chart controls. The chart controls are then added to "display's" controls collection (Controls.Add(chart)), which completes the loading of display, and then display is added to "monitor's" control collection, and the form is shown.

The problem I have is that each chart control takes about 0.5s to load, and we can have around 75 of them in one display object. Therefore, I would like to create these charts in parallel, to speed up the load time. I took advantage of the TPL method Parallel.ForEach to accomplish this.

My code looks like this, inside the "display" object's constructor:

public class DisplayControl : UserControl
{
    public DisplayControl()
    {
        var chartNames = new string[] { "chart1", "chart2", ... };
        var builtCharts = new ConcurrentBag<Chart>();

        // Create the chart objects in parallel
        Parallel.ForEach(chartNames, name =>
            {
                // The chart constructor creates the chart itself, which is a 
                // custom user control object (inherits UserControl), composed  
                // of other various WinForm controls like labels, buttons, etc.
                var chart = new Chart();
                chart.Name = name;
                builtCharts.Add(chart);
            }
        );

        // Clean up the charts and add them to "display's" control collection
        foreach(var chart in builtCharts)
        {
            // Do some unimportant modifications to the chart, synchronously
            ...

            this.Controls.Add(chart);
        }
    }
}

The DisplayControl constructor is called on the main thread by the main form, Monitor, and the DisplayControl instance itself is added to Monitor's ControlCollection. Monitor is then shown using Monitor.Show(), which is just the Show method of a Form class.

The main issue I am experiencing is that the DisplayControl constructor throws an InvalidOperationException occasionally at the this.Controls.Add line, citing the following:

System.InvalidOperationException: Cross-thread operation not valid: Control 'panel' accessed from a thread other than the thread it was created on.

I should note that panel is just a panel WinForms control, which is created in the Chart() constructor.

Finally, the code appears to work just fine about 90% of the time, and these errors are seemingly random. If I get the error, I can usually just re-run the code immediately and it will work.

My goal is to eliminate this error and make this all thread-safe.

ccampo
  • 1,483
  • 3
  • 12
  • 14

1 Answers1

2

All operations regarding System.Windows.Forms must be executed in the main thread. There are numerous questions about this; like here. So if you're just creating the charts in your parallel loop, then better keep it as a plain foreach.

Community
  • 1
  • 1
Matthias
  • 15,919
  • 5
  • 39
  • 84
  • The only problem I have with this is that the charts take on the order of 0.5 seconds to even return from the constructor, and when you have ~75 of these, there's about a 40 second load time. – ccampo Oct 30 '13 at 21:12
  • 3
    @ccampo: Maybe you can do whatever the constructor is doing asynchronously. – Michael Liu Oct 30 '13 at 21:14
  • There's not much you can do afaik. You could try to optimize the code of the `Chart` class, or maybe cache the form, instead of closing and disposing it. – Matthias Oct 30 '13 at 21:14
  • @MichaelLiu is right. Classes and methods that don't belong to the forms namespace, can be executed asynchronously. – Matthias Oct 30 '13 at 21:16
  • @MichaelLiu @Matthias I really wish I could, but all that `Chart()` constructor is doing is... creating more WinForm controls! – ccampo Oct 30 '13 at 21:17
  • Is it a library or your own implementation? – Matthias Oct 30 '13 at 21:18
  • Inside the `Chart()` constructor, it is basically creating a bunch of third party charting controls, as well as some other things like labels, etc.. The `Chart` control itself is our own implementation, but it is just a collection of WinForm and 3rd party WinForm controls – ccampo Oct 30 '13 at 21:21
  • Depending on how much controls are involved, it might be better to paint the control yourself. – Matthias Oct 30 '13 at 21:22