-4

I made a C# WinForms application where I plot thousands of real time data points by using charts. I have noticed that during my application is running when I turn on lets say a web-browser the plot freezes. I tried to plot less points but it seems one never knows which program in parallel will be executed so I'm afraid the CPU usage of other programs depending on the PC will effect the performance.

edit:

        private void button1_Click(object sender, EventArgs e)
        {
///


            _cts = new CancellationTokenSource();
            _infiniteLoop = InfiniteLoop(_cts.Token);



}




        private async Task InfiniteLoop(CancellationToken cancellationToken = default)
        {
            ushort[] ushortArray = null;
            while (true)
            {
                Task loopMinimumDurationTask = Task.Delay(100, cancellationToken);
                Task<ushort []> calculationTask = Task.Run(() => Calculate());
                if (ushortArray != null) PlotData(ushortArray);
                ushortArray = await calculationTask;
                await loopMinimumDurationTask;
            }
        }

        public  ushort [] Calculate()
        {
            init();
            daq.ALoadQueue(chArray, chRange, CHANCOUNT);

            ScanOptions options = ScanOptions.Background | ScanOptions.Continuous | ScanOptions.ConvertData;
            //setup the acquisiton
            UL = daq.AInScan(FIRSTCHANNEL, SINGLE_KANAL_NUM, BUFFERSIZE, ref Rate, Range.Bip10Volts, buffer, options);
            UL = daq.GetStatus(out daqStatus, out Count, out Index, FunctionType.AiFunction);


            if ((Index >= HALFBUFFSIZE) & ReadLower) //check for 50% more data
            {
                //get lower half of buffer
                UL = MccService.WinBufToArray(buffer, ushortArray, 0, HALFBUFFSIZE);
                ReadLower = false; //flag that controls the next read

      

                return ushortArray;

            }

            else if ((Index < HALFBUFFSIZE) & !ReadLower)
            {
                //get the upper half
                UL = MccService.WinBufToArray(buffer, ushortArray, HALFBUFFSIZE, HALFBUFFSIZE);
                ReadLower = true;//flag that controls the next read

      

                return ushortArray;

            }

            return null;
        }


        public void PlotData(ushort[] datArray_Plot)
        {

            ////////Thread.Sleep(10);
            SerialList1.Clear();

            for (int b = 0; b < HALFBUFFSIZE; b++)
            {
                UL = (daq.ToEngUnits(Range.Bip10Volts, datArray_Plot[b], out temp2));
                SerialList1.Add(temp2);
                SerialList2.Add(temp2);
                ikb_p = ikb_p + 1;
            }

            int out_size = SerialList1.Count / h; //size of downsampled array

            if (out_size <= 2)
                out_size = 2;

            array = SerialList1.ToArray(); //original array

            if (h != 1)
                array = Downsample(array, out_size); //downsampled array

            if (ikb_p > BUFFERSIZE)
            {

                chart1.Series["Ch0"].Points.SuspendUpdates();
                for (int b = 0; b < out_size; b++)
                {
                    chart1.Series["Ch0"].Points.AddY(array[b]); //Plots each sample or  use chart1.Series["Ch0"].Points.DataBindY(array);

                    if (chart1.Series["Ch0"].Points.Count > display_seconds * FREQ / h)
                    {
                        chart1.Series["Ch0"].Points.RemoveAt(0);
                    }

                }

                //chart1.Series["Ch0"].Points.ResumeUpdates();
                chart1.Invalidate();

            }




            //FFT
            if (SerialList2.Count > 4 * HALFBUFFSIZE / CHANCOUNT)
            {
                chart2.Series["Freq"].Points.Clear();
                float sampling_freq = (float)FREQ;
                float[] data = SerialList2.ToArray();

                double[] dftIn = new double[data.Length];
                double[] dftInIm = new double[data.Length];
                double[] DftIn = new double[data.Length];
                double[] FFTResult = new double[data.Length];
                double[] f = new double[data.Length];
                double[] power = new double[data.Length];

                double[] window = MathNet.Numerics.Window.Hamming(data.Length);

                for (int i = 0; i < data.Length; i++)
                {
                    dftIn[i] = window[i] * (double)data[i];
                }

                for (int i = 0; i < data.Length; i++)
                {
                    dftInIm[i] = 0.0;
                }

                FFT(dftIn, dftInIm, out reFFT, out imFFT, (int)Math.Log(data.Length, 2));

                for (int i = 0; i < data.Length / 2; i++)
                {
                    if (i > 0)
                    {
                        float a = sampling_freq / (float)data.Length;
                        float x = (float)i * a;
                        double y = Math.Sqrt(reFFT[i] * reFFT[i] + imFFT[i] * imFFT[i]);

                        f[i] = x;
                        FFTResult[i] = 2 * y / (data.Length / 2);

                        power[i] = 0.5 * FFTResult[i] * FFTResult[i];
                    }
                }

                double scale = data.Length / sampling_freq;

                chart2.Series["Freq"].Points.DataBindXY(f, power);

                float stdCh0 = 0;
                float avg1 = SerialList2.Average();
                float max1 = SerialList2.Max();
                float min1 = SerialList2.Min();
                float sum1 = (float)SerialList2.Sum(d => Math.Pow(d - avg1, 2));
                stdCh0 = (float)Math.Sqrt((sum1) / (SerialList2.Count() - 1));

                label5.Text = avg1.ToString("0.000000");
                label22.Text = stdCh0.ToString("0.000000");
                label70.Text = max1.ToString("0.000000");
                label61.Text = min1.ToString("0.000000");

                SerialList2.Clear();
                label1.Text = count_sample.ToString();

            }

            ///progressBar1
            double ratio = (double)count_sample / (seconds * FREQ);
            if (ratio > 1.000)
                ratio = 1;
            progressBar1.Value = (Convert.ToInt32(1000 * ratio));
            progressBar1.Invalidate();
            progressBar1.Update();

            //Display event handlers
            if (comboBox2_changed == true)
            {
                if (comboBox2.SelectedIndex == 0)
                {
                    //chart1.ChartAreas[0].RecalculateAxesScale();
                    chart1.ChartAreas[0].AxisY.IsStartedFromZero = false;
                }
                if (comboBox2.SelectedIndex == 1)
                {
                    //chart1.ChartAreas[0].RecalculateAxesScale();
                    chart1.ChartAreas[0].AxisY.IsStartedFromZero = true;
                }
                comboBox2_changed = false;
            }

            if (comboBox1_changed == true)
            {
                if (comboBox1.SelectedIndex == 0)
                {
                    chart1.Series["Ch0"].ChartType = SeriesChartType.FastLine;
                }
                else
                    chart1.Series["Ch0"].ChartType = SeriesChartType.FastPoint;
            }

            if (num_updown1_changed)
            {
                display_seconds = (float)numericUpDown1.Value * 0.001f;
                h = (int)numericUpDown2.Value;
                chart1.Series["Ch0"].Points.Clear();
                //chart1.ChartAreas[0].AxisX.Maximum = display_seconds * FREQ / h;
                num_updown1_changed = false;

                int avg = (int)((double)FREQ * (Decimal.ToDouble(numericUpDown1.Value) / 1000.0) / max_chart_points);
                if (avg != 0)
                    numericUpDown2.Value = avg;
            }

            if (num_updown2_changed)
            {
                display_seconds = (float)numericUpDown1.Value * 0.001f;
                h = (int)numericUpDown2.Value;
                chart1.Series["Ch0"].Points.Clear();
                //chart1.ChartAreas[0].AxisX.Maximum = display_seconds * FREQ / h;
                num_updown2_changed = false;

            }

        }



        private void Form_FormClosing(object sender, FormClosingEventArgs e)
        {
            _cts.Cancel();
            // Wait the completion of the loop before closing the form
            try { _infiniteLoop.GetAwaiter().GetResult(); }
            catch (OperationCanceledException) { } // Ignore this error
        }
floppy380
  • 179
  • 1
  • 8
  • 1
    Is your primary goal to a) be the only program running on a dedicated machine (in which case, why are people able to launch anything else) or b) a "good citizen" on a machine shared by multiple programs? Anything else seems a little rude, if the user has decided they'd rather spend time using a browser than paying attention to your program. – Damien_The_Unbeliever Feb 10 '21 at 09:11
  • Well, you could try boosting the priority of your application - this can be done from Windows Task Manager (as well as via code). This isn't normally recommended, though. – 500 - Internal Server Error Feb 10 '21 at 09:12
  • So you want the operating system to prioritize your program over other programs, regarding the CPU resources of the PC? – Theodor Zoulias Feb 10 '21 at 09:12
  • Primary goal only program running on a dedicated machine but the user might at the same time open another application which is not in my control. I would like to allocate certain amount of fixed CPU but I dont know whether it is possible at all. – floppy380 Feb 10 '21 at 09:13
  • Yes I want to boost priority by using code or if not by OS – floppy380 Feb 10 '21 at 09:16
  • I just set the priority to the max and still causes freezing when I open a a browser and with youTube it stops completely. So Task manager trick is not working – floppy380 Feb 10 '21 at 09:19
  • What do you mean by "dedicated machine"? A machine dedicated for a single user, or a machine dedicated for a single program? – Theodor Zoulias Feb 10 '21 at 09:24
  • Sounds like you need to learn GPU (graphics card) programming – Charlieface Feb 10 '21 at 09:25
  • @Charlieface Would be great if there was an easy OpenGL library for windows. The ones I encountered was too difficult to learn since I have limited time frame. – floppy380 Feb 10 '21 at 09:35
  • It seems that you are doing more work in the `ProgressChanged` event than in the `DoWork` event. Also you are not using the `e` parameter of the `ProgressChanged` event, so it is a mystery how it gets the data required to report the progress. From the looks of it your are misusing the `BackgroundWorker` component, which may not be even suitable for your use case. This component expects that the UI-related work is negligible compared to the background work, which is obviously not the case here. – Theodor Zoulias Feb 10 '21 at 10:03
  • @TheodorZoulias Do you recommend to move most of the calculations to DoWork? And shouldnt I use the chart plotting inside the ProgressChanged? – floppy380 Feb 10 '21 at 10:08
  • Honestly my recommendation is to abandon (or delete) this question, because its basic premise, that the operating system is doing a bad job at prioritizing the CPU resources between processes, is almost certainly wrong. And then post a new question asking why your code causes the UI to become sluggish, and how you could modify it in order to prevent that from happening. – Theodor Zoulias Feb 10 '21 at 10:15
  • @TheodorZoulias I changed the question. I couldn't open another one due to question limit reached. – floppy380 Feb 10 '21 at 10:19
  • I didn't know that a question limit exists. Unfortunately this question has already received an answer. Changing drastically its title and body is not going to be perceived well IMHO. – Theodor Zoulias Feb 10 '21 at 10:22
  • @TheodorZoulias Who will perceive it? I just need help – floppy380 Feb 10 '21 at 10:50
  • @floppy380 Edits are for clarifying issues. Please do not change the main point of a question. See [Update or post a new question](https://meta.stackexchange.com/questions/106249/update-a-question-or-post-a-new-question) – JonasH Feb 10 '21 at 10:56
  • @JonasH Im not allowed to post a new one due to limit. I tried it and I need help a bit urgent. I know its not good but that was the only option. – floppy380 Feb 10 '21 at 11:03

2 Answers2

0

You could use threadpriority:

Thread.CurrentThread.Priority = ThreadPriority.Highest;

This is however considered to be poor form in most cases since the operating system is in a better position to decide what program deserves CPU time. And it does not need to follow your request for more time, even if you explicitly ask for it.

If plotting takes a considerable amount of time you might consider:

  • Can you optimize the plotting somehow?
  • Can you reduce the number of points?
    • you could perhaps plot a smaller part of the dataset?
    • You could pre-process the plot to reduce the point density. Screens typically have a resolution of 2k-4k, so if you have a line-chart with more points the user will not be able to see it anyway.
JonasH
  • 28,608
  • 2
  • 10
  • 23
  • I already down sample the points 400 times meaning that reducing the number of points by averaging. But there is a trade off here between CPU freezing and plot's form destruction due to averaging. What is the maximum frequency of a sine one can see(notice) for a 1 second of chart frame? Can one see more than 1000 Hz for instance? Because all the wave forms are composite of sine waves. So if I could know it I could adjust the max down sampling versus the time frame of the chart. – floppy380 Feb 10 '21 at 09:29
  • @floppy380 it depends. according to Nyquist–Shannon it would be sufficient with two times the number of horizontal pixels in your chart. But the screen does not have a correct reproduction filter, and may have antialiasing, so some multiple of the horizontal resolution should probably be appropriate. If there is an issue plotting a few thousand points I would consider using some other plotting method. Perhaps just do the drawing yourself. – JonasH Feb 10 '21 at 09:39
  • @floppy380 is it possible that you are doing computations on the UI thread, that could be done on a background thread instead? If that's the case, you could consider offloading all non-UI related work on background threads. Or improving your algorithms so that they do more work using less CPU. – Theodor Zoulias Feb 10 '21 at 09:40
  • @TheodorZoulias I use background worker and the chart plots in when the worker ProgressChanged event. – floppy380 Feb 10 '21 at 09:44
  • 1
    @floppy380 how frequent do you invoke the `ProgressChanged` event? – Theodor Zoulias Feb 10 '21 at 09:45
  • @TheodorZoulias I do it inside a while loop. I will add that part of the program now just a second. – floppy380 Feb 10 '21 at 09:48
  • @TheodorZoulias I added to my question backgroundWorker1_DoWork and ProgressChanged code. – floppy380 Feb 10 '21 at 09:49
  • Freezing issue is in chart1 which plots the averaged signal – floppy380 Feb 10 '21 at 09:50
0

My suggestion is to scrap the obsolete BackgroundWorker, in favor of an infinite asynchronous loop. The example below assumes the existence of a Calculate method that should run on a background thread and should return the result of one calculation, and an UpdateUI method that should run on the UI thread and should consume this result.

private async Task InfiniteLoop(CancellationToken cancellationToken = default)
{
    object calculationResult = null;
    while (true)
    {
        Task loopMinimumDurationTask = Task.Delay(100, cancellationToken);
        Task<object> calculationTask = Task.Run(() => Calculate());
        if (calculationResult != null) UpdateUI(calculationResult);
        calculationResult = await calculationTask;
        await loopMinimumDurationTask;
    }
}

This design has the following characteristics:

  1. The Calculate and the UpdateUI methods are working in parallel.
  2. If the Calculate completes first, it waits the completion of the UpdateUI before starting the next calculation.
  3. If the UpdateUI completes first, it waits the completion of the Calculate before starting the next update of the UI.
  4. If both the Calculate and the UpdateUI complete in under 100 milliseconds, an extra asynchronous delay is imposed, so that no more than 10 loops per second can occur.
  5. The infinite loop can be terminated by canceling the optional CancellationToken.

The object type for the calculationResult variable in the above example is just for demonstration. Unless the result of the calculation is trivial, you should create a class or struct that can store all the data required for updating the UI on every loop. By eliminating all global state you minimize the number of things that can go wrong.

Usage example:

private CancellationTokenSource _cts;
private Task _infiniteLoop;

private void Form_Load(object sender, EventArgs e)
{
    _cts = new CancellationTokenSource();
    _infiniteLoop = InfiniteLoop(_cts.Token);
}

private void Form_FormClosing(object sender, FormClosingEventArgs e)
{
    _cts.Cancel();
    // Wait the completion of the loop before closing the form
    try { _infiniteLoop.GetAwaiter().GetResult(); }
    catch (OperationCanceledException) { } // Ignore this error
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • Hi Theodor thank you for this. I embedded your suggestion to my code. I dont get any syntax error in Visual Studio but when I run the application and click on button1 I dont see any plot either. Something is not invoked. Can you look at my code in my question I put it there. Some how I might be doing something wrong. In my case Calculate() will run in loop and produce ushort array in there. And this retuned ushortArray will be processed by PlotData which is the mostly UI part. I embedded your code but might be doing something wrong. – floppy380 Feb 10 '21 at 20:22
  • @floppy380 I am seeing a dependency inside the `PlotData` on two undefined variables `SerialList1` and `SerialList2`. Could you make sure that this method uses only its argument (`ushort[] datArray_Plot`) as the data for plotting the chart? Btw I have not actually tested the code I posted to see if it's working. You could test it with some dummy/simplified logic to see if it's working or not. Something like updating a `Label`, and putting a Thread.Sleep(50) to simulate some CPU intensive work. – Theodor Zoulias Feb 10 '21 at 20:38
  • Only data used in PlotData is the ushortArray which is passed into it. SerialLists are stroring this array data for some computations. daq.ToEngUnits method converts this array's units to another. So all in all there is no other input data besides ushortArray. Thread.sleep(50) did not help either. Im more suspicious whether I embedded your code wrong. – floppy380 Feb 10 '21 at 20:48
  • @floppy380 be aware that in case of an exception inside the `InfiniteLoop`, the exception will not be immediately surfaced, and will not crash the process. The exception will be stored in the `_infiniteLoop` task. Obviously the loop will be stopped. You could [attach a continuation](https://stackoverflow.com/questions/8932805/using-the-net-4-task-library-to-display-a-blocking-message-box-in-wpf) on this task, so that in case of an exception to popup immediately a `MessageBox` or something. – Theodor Zoulias Feb 10 '21 at 20:52
  • @floppy380 if you want any error in the `InfiniteLoop` task to be fatal, add this `async void` method somewhere: `public static async void OnErrorCrash(Task task) => await task;`, and call it after creating the task: `OnErrorCrash(_infiniteLoop);`. This way any error will be rethrown on the current synchronization context, and will have the same effect as an unhandled exception inside a normal event handler. – Theodor Zoulias Feb 10 '21 at 21:08