BackgroundWorker and DrawGraph

Jul 20, 2012 at 11:35 PM

Hi,

I've a strange problem I cannot escape and figure out.

I've my Windows Form application with its ElementHost.Child that normally hosts the NodeXLControl. By clicking a button, I do this:

 

private void SignificantDrawButtonClick(object sender, EventArgs e)
{
            if (loadGraphBackgroundWorker.IsBusy != true)
            {
                loadGraphBackgroundWorker.RunWorkerAsync();
            }
}

 

which substantially loads vertices and edges:

 

private void LoadGraphBackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
        {
            var worker = sender as BackgroundWorker;

            if (worker != null && worker.CancellationPending)
            {
                e.Cancel = true;
            }
            else
            {
                LoadVertices(worker);
                LoadSignificantEdges(worker);
            }
        }

 

into the _graph object I've initialized and assigned within the constructor:

 

_graph = new Graph(GraphDirectedness.Undirected);
_nodeXlControl = new NodeXLControl { Graph = _graph };
GraphElementHost.Child = _nodeXlControl;

 

 

The problem comes here. If I deal with small graphs, the following works (namely, it draws the graph in the _nodeXLControl). 

private void LoadGraphBackgroundWorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
                progressToolStripSplitButton.Text = "Canceled!";
            }
            else if (e.Error != null)
            {
                progressToolStripSplitButton.Text = ("Error: " + e.Error.Message);
            }
            else
            {
		_nodeXlControl.DrawGraph(true);
} }

 

When I use a graph with 170 vertices and some 3000 edges, it seems to stuck (namely, the layout seems to never terminate to work -> the graph is never drawn).
BUT, if I comment the line: _nodeXlControl.DrawGraph(true); and let the BackgroundWorker finish. Then, If I click another button with this event:

private void SomeButtonClick(object sender, EventArgs e)
        {
           _nodeXlControl.DrawGraph(true);
        }

The graph, even if big, is drawn in 2 or 3 seconds.

Why? How can I overcome this issue?

Many thanks
Tommaso
Jul 23, 2012 at 7:35 AM
Edited Jul 23, 2012 at 7:38 AM

Tommaso:

Are you accessing the NodeXLControl directly from your BackgroundWorker.DoWork event handler?  If so, you cannot do that.  Accessing a UI control directly from any thread other than the UI thread is a cardinal sin in Windows.

If loading vertices and edges is slow in your application and you are using a BackgroundWorker to avoid locking up the UI, then you should be loading the vertices and edges in either the BackgroundWorker.ProgressChanged event or the BackgroundWorker.RunWorkerCompleted event, both of which get called on the UI thread.  I don't know if that's going to fix the problem you're seeing, but it's a required first step.

By the way, do not be encouraged by the observation that your code works with small graphs.  If it fails with large graphs then the code isn't correct, period, and the fact that it works for any graphs at all should be considered just a random occurrence.

-- Tony

Jul 23, 2012 at 9:03 AM

Hi Tony,

no, I am not accessing the NodeXLControl directly from the DoWork event handler. Or, at least, what I do is to load the _graph.Vertices and _graph.Edges collections with information taken from file. Is that wrong? Namely, accessing the Graph object of the NodeXLControl means accessing to the NodeXLControl directly? In this case, you are right and I'm a sinner!

 

 

private void LoadVertices(BackgroundWorker worker)
        {
            var oVertices = _graph.Vertices;
            var vertexLabels = GetRawVertexLabels().ToList();
            var verticesCount = vertexLabels.Count;

            for (var i = 0; i < verticesCount; i++)
            {
                var label = vertexLabels[i];
                var percentage = Math.Round((i + 1) * 100 / (double)verticesCount);
                worker.ReportProgress((int)percentage, "Loading vertices...");

                var v = oVertices.Add();
                v.SetValue(ReservedMetadataKeys.PerColor, _vertexColor);
                v.SetValue(ReservedMetadataKeys.PerVertexRadius, VertexSize);
                v.SetValue(ReservedMetadataKeys.PerVertexLabel, label);
                v.Name = label;
            }
        }

 

 

Let me know,

Thanks

Tommaso

Jul 23, 2012 at 6:11 PM

Tommaso:

Your _graph object belongs to the NodeXLControl, you are accessing the _graph object from your DoWork event handler, and so yes, you are accessing the NodeXLControl directly from your DoWork event handler.  The NodeXLControl is not thread-safe (most controls aren't), so you cannot do that.

What you can do instead is to create your own Graph object using "_graph = new Graph(directedness)" and populate that object from DoWork.  The object will not be connected in any way to the NodeXLControl, so you will not be violating any threading rules by populating it in a background thread.  Then, in your RunWorkerCompleted event handler, set NodeXLControl.Graph = _graph and call NodeXLControl.ShowGraph().  RunWorkerCompleted runs on the UI thread, so you will not be violating any threading rules there, either.

-- Tony

Jul 24, 2012 at 5:01 PM

Hi Tony,

your suggestion definitely fixes my problem!

 

Many thanks

Tomamso

Jul 24, 2012 at 5:31 PM

That's good news.  Thanks for letting me know.

-- Tony