Measurement Studio for .NET Languages

cancel
Showing results for 
Search instead for 
Did you mean: 

Can't get Graph.DataSource to work in WPF i (threading issue?)

Solved!
Go to solution

My Graph is working beautifly when I use:

 

chartCollection.Append(measurement.GHz, measurement.dB);

 

But when I try to use this call in the same method, I get nothing on the graph:

 

double[] data = new double[256];
....
graph.DataSource = data;

 

 

I don't know if this is a hint but I declare chartCollection on the main thread and then pass it on to a different thread to gather data and plot it in real time.

I could have lived with chartCollection.Append but in this case I gather a lot of data instanteneusly and I want to assign it to Graph at once.

By the way, it seems like you need about 350ms delay between each chart.Append not to cause exception... but in my case I have to delay in any case (to get data) so it's not an issue.

Thank you!

0 Kudos
Message 1 of 16
(5,999 Views)

First, the ChartCollection class is not thread-safe: changes made outside of the UI thread are not guaranteed to produce correct results. Instead, updates should be queued to the UI dispatcher.

 

Similarly, DataSource is a WPF dependency property, which means it can only be accessed from the UI thread. If you try to assign DataSource on another thread, WPF will throw an InvalidOperationException.

 

Threading issues aside, if you assign a double[] to a graph, the graph should display a single plot using the values in the array. The visibility of those values will depend on the data (i.e. NaN values will display as gaps; constant values may be confused with grid lines), the configuration of the axes (i.e. if your data goes from 0 to 100 and the axis range is fixed at 1000 to 2000, all the data will be off screen), the visibility of the plot, the renderer configuration, etc.

~ Paul H
Message 2 of 16
(5,985 Views)

Thanks for the quick reply.

Do you have a very simple exaple on what's the best way to do this?

I definitely need to gather my measurements on a different thread because I have Thread.Sleep calls in it (for instruments to settle, etc).  I guess I don't have to plot in real time if I can't mix the main UI thread and secondary thread to collect data... but I do need to plot it when I am done collectiing data.

 

Are you saying that I can/must stash away an array I populate on a secondary thread and then do Graph.DataSource = array after I get back to the main UI thread?  Is this the only/best way to do this?

Thanks

0 Kudos
Message 3 of 16
(5,980 Views)
Solution
Accepted by topic author kirko7

I am not very clear on how you are mixing the chart/array updates. Here is an example that queues chart appends through the dispatcher (using a task and helper class as a quick way to simulate background work). You can use any delegate with the dispatcher, so you could assign DataSource in a delegate instead of use the append method on a chart collection.

 

    public partial class MainWindow : Window {
        private readonly ChartCollection<int, double> chart = new ChartCollection<int, double>( );

        public MainWindow( ) {
            InitializeComponent( );

            this.graph.DataSource = chart;

            var worker = new Worker( this.Dispatcher, chart.Append );
            Task.Factory.StartNew( worker.DoWork );
        }

        private class Worker {
            private readonly Dispatcher dispatcher;
            private readonly Action<int, double> append;

            public Worker( Dispatcher dispatcher, Action<int, double> append ) {
                this.dispatcher = dispatcher;
                this.append = append;
            }

            public void DoWork( ) {
                for( int i = 0; i <= 360; ++i ) {
                    double value = Math.Sin( i * Math.PI / 180 );
                    this.dispatcher.BeginInvoke( this.append, i, value );
                    Thread.Sleep( 100 );
                }
            }
        }
    }

~ Paul H
Message 4 of 16
(5,976 Views)

My threading structure is very different so I have to decide what's the best approach... 

This is what I have:

 

public void RunTest()
{
ChartCollection<double, double> chartCollection = new ChartCollection<double, double>(dataSize);

graph.Data.Add(chartCollection);

Action actionRunRfBaseFuncTest = delegate { BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += delegate { GetMeasurements(chartCollection); }; worker.RunWorkerCompleted += delegate { }; worker.RunWorkerAsync(); }; MainWindowDispatcher.BeginInvoke(DispatcherPriority.Background, actionRunRfBaseFuncTest);
}

public void GetMeasurements(ChartCollection<double, double> chartCollection)
{
double GHz;
double dB;

foreach (int f = 0; f < 200; f++)
{
PNA.SetFrequency(f);
Thread.Sleep(350);
PNA.GetPower(ref double GHz, ref double dB);

chartCollection.Append(GHz, dB);
}
}

 

Does this look like a really bad design?  I'd really appreciate your feedback.  (I left out all unrelated code)

Thank you

 

0 Kudos
Message 5 of 16
(5,968 Views)
Solution
Accepted by topic author kirko7

Your overall design looks fine. Your GetMeasurements method is the equivalent of the DoWork method in my example, so making similar changes (using a dispatcher and append action instead of chartCollection) should marshal updates to the UI thread correctly. For example:

 

    public void GetMeasurements(Dispatcher dispatcher, Action<double, double> append)
    {
        double GHz;
        double dB;

        for (int f = 0; f < 200; f++)
        {
            PNA.SetFrequency(f);
            Thread.Sleep(350);
            PNA.GetPower(ref GHz, ref dB);

            dispatcher.BeginInvoke(append, GHz, dB);
        }
    }

~ Paul H
Message 6 of 16
(5,965 Views)

Nice!!  This works beautifully and  now I don't even need any sleep in order not to crash UI thread.

So this is a perfect example on how to use chartCollection.Append but now I have a follow up question... this would help me hugely.

 

I also have a function where I need to plot data at once after I collected it.  I could loop through my array and use chartCollection.Append but I'm sure there's a better way... and I'm sure it's graph.DataSource = array

 

I'm just not sure how to "marshal" it in this case.  Could you please provide this kind of example?

Thank you for all your help!

 

0 Kudos
Message 7 of 16
(5,956 Views)
Solution
Accepted by topic author kirko7

You just need to use a different delegate. The measurement method would look like:

 

public void GetMeasurements(Dispatcher dispatcher, Action<object> plot) {
    ...
    dispatcher.BeginInvoke(plot, data);
    ...
}

 

:and you could call it with a lambda delegate:

 

GetMeasurements(Dispatcher, data => graph.DataSource = data)

~ Paul H
Message 8 of 16
(5,942 Views)

Thank you!

And I'm pretty sure it's my last question on this topic.

If I have like 16 ChartCollections to append in the same method on a different thread, what's the best way to marshal it?  At the moment I marshal them like you showed (basically I have 16 arguments into this method) and it works nicely.  I was just wondering if there's a "smarter" way of doing it.

 

This thread has been extremely helpful!

0 Kudos
Message 9 of 16
(5,939 Views)
Solution
Accepted by topic author kirko7

Marshaling 16 separate Append calls will work correctly. If you see a performance issue where updates start to lag, you could change it to work more like the second approach (marshaling one call with an array of updates, and doing chart[i].Append( time[i], value[i] ) in the delegate). The graph will behave the same for either approach.

~ Paul H
Message 10 of 16
(5,937 Views)