Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Drawing several charts in real time, C# forms

I'd like to get a preview from 16 channels of my A/C and I need the refresh rate at 25-40Hz (update every 25-40ms). I made few combinations of threads and timers but I achieved satisfying performance only for at most 4 charts. After extending the range of charts to refresh, the frame rate of added charts was about 0.5/s. I applied fastline chart.
I applied a timer to get new data from A/C every 20ms. After some tests it looks that adding a separate thread to deal with each chart, which sleeps for a given time and then updates the plot is not efficient (at least in the manner I did it).
So the question is: how to efficiently handle with multiple charts.
Below I present most important parts of the code.

    System.Timers.Timer tim = new System.Timers.Timer(20);
    System.Threading.Thread[] t;
    int HighChan = 15;
    //button which runs the preview, executed once
    private void previewB_Click(object sender, EventArgs e)
    {                                            
        t = new System.Threading.Thread[HighChan + 1];
        for (int i = 0; i <HighChan+1; i++)
        {                    
            charts[i].Series.SuspendUpdates();
            //run a separate thread for each chart
            t[i] = new System.Threading.Thread(new ParameterizedThreadStart(updatePlot));
            t[i].Start(i);                    
        }

        //run timer to get new data with tim.interval                    
        tim.Stop();               
        tim.Elapsed += new ElapsedEventHandler(this.OnTimedEvent);           
        tim.Start();                                
    }

    ushort[] ADData_prev;
    //get new data from A/C every tim.interval
    private void OnTimedEvent(object sender, EventArgs e)
    {
        ADData_prev = getPrev();  //gets new data, array wit 16 fields
        // I also tried to suspend and resume threads t from this place but unsuccessfully
    }


    //update the chart with new data
    void updatePlot(object chart_info)
    {          
        int i = (int)chart_info;

        while(true)
        {
                //wait for new data to read
                Thread.CurrentThread.Join(20);

                charts[i].Invoke(new Action(delegate()
                { charts[i].ResetAutoValues(); }));

                // I skipped some conditions to make code clearer                              
                //remove old point and add new one
                charts[i].Invoke(new Action(delegate()
                {
                    charts[i].Series[0].Points.RemoveAt(0);
                    charts[i].Series[0].Points.AddY(ADData_prev[i]);
                }));                     

                charts[i].Invoke(new Action(delegate()
                {
                    charts[i].Series.ResumeUpdates();
                    charts[i].Series.Invalidate();
                    charts[i].Series.SuspendUpdates();
                }));

          }
    }

Update:
1.I have moved the functionality of updatePlot() to timer's onTimedEvent so now it looks like that:

    private void OnTimedEvent(object sender, EventArgs e)
    {
        ADData_prev = getPrev();  //gets new data, array wit 16 fields

        charts[0].Invoke(new Action(delegate()
        {

           for (int i = 0; i < HighChan + 1; i++)
           {
               //charts[i] stuff here
           }
        }
    }

2.I decided to change the bool variable in onTimedEvent which allows to draw charts once, every timer's tick in while(true) loop in updatePlot():

    private void previewB_Click(object sender, EventArgs e)
    {                                            
        for (int i = 0; i <= HighChan; charts[i++].Series.SuspendUpdates()) ;
        t = new System.Threading.Thread(updatePlot);
        t.Start();

        //run timer to get new data with tim.interval                    
        tim.Stop();               
        tim.Elapsed += new ElapsedEventHandler(this.OnTimedEvent);           
        tim.Start();                                
    }

    bool entry = false;
    private void OnTimedEvent(object sender, EventArgs e)
    {
        ADData_prev = getPrev();  //gets new data, array wit 16 fields
        entry = true;            
    }

    void updatePlot()
    {
       while(true)
       {
          if(entry)
          {
               charts[0].Invoke(new Action(delegate()
               {
                  for (int i = 0; i < HighChan + 1; i++)
                  {
                     //charts[i] stuff here
                  }
               }
          entry = false;
          }
       }
    }

These solutions made only very slight improvement. Unfortunately, still, in both cases, charts aren't refreshed quickly enough. Additionally, first 8 from the charts[] array are refreshing fast, while the last 8 have the frame rate of about 0.5 Hz what is quite strange for me that they not behave in the same way.


Update 2:

As in the first post, I need to redraw charts every 20-40ms. When I draw only one plot I can achieve this frame rate so it is not a matter of data collection time (the A/C works in background with Fs=1k Hz). Maybe binding queue to a chart made it faster but not to much.

I'll describe more precisely what has happened in the performance tests which I conducted.
So, when I limit nr (number of charts to refresh) to 6, update them with ti (time interval) 20ms, so they store number of points (np) = 50 at a moment, they all run smooth. But, when I change the np to 100, only charts 1-4 run smooth, when 5 slows down very much and 6 almost stops. When nr = 6, ti = 20, np = 250, the charts 1-3 run smoothm while charts 4-6 run like 0.1fps.

When nr = 10, ti = 20, np = 50, the charts 1-6 behave in the same way as in nr=6 case, while charts 7-8 surprisingly run smooth and 9-10 are like 1fps. When nr = 10, ti = 20, np = 100, the charts 1-6 behave in the same way as in nr=6 case (so only charts1-3 run smooth), while charts 7-8 still run smooth and 9-10 are like 0.1fps.

When nr = 16, ti = 20, np = 50, charts 1-8 behave like in nr=10 case and the other 8 are like 0.05fps or something.

Whichever method I use, with single thread, multiple threads, single/multiple Invoke, Queue binding, Thread.Sleep(), Threed.CurrentThread.Join(), Timers, async the result is always more less the same. Honestly I don't know what can be the reason why I can't get real time data preview on 16 fastline charts.
I'd be thankful for any advice.

like image 506
Bricker Avatar asked Mar 15 '14 12:03

Bricker


1 Answers

I don't think multithreading is going to help you at all here, to be honest. At the end of the day the heaviest operation is going to be the updating of the charts, which has to be done on the UI thread anyway.

I would suggest you have a single thread which every 20ms gets all the latest values, then calls a single Invoke on the UI which updates all charts at once within a single Action. Invoking multiple times per update incurs a significant performance penalty.

like image 102
Chris Ballard Avatar answered Oct 19 '22 07:10

Chris Ballard