如何防止 backgroundWorker 导致 UI 变得缓慢?

How can I prevent the backgroundWorker causing the UI to become sluggish?

我制作了一个 C# WinForms 应用程序,我在其中使用图表绘制了数千个实时数据点。我注意到在我的申请期间 运行 当我打开网络浏览器时,情节冻结了。我试图绘制更少的点,但似乎永远不知道将执行哪个并行程序,所以我担心 CPU 根据 PC 使用其他程序会影响性能。

编辑:

        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
        }

您可以使用 threadpriority:

Thread.CurrentThread.Priority = ThreadPriority.Highest;

然而,在大多数情况下,这被认为是一种糟糕的形式,因为操作系统可以更好地决定哪个程序值得 CPU 时间。即使您明确要求,它也不需要更多时间来满足您的要求。

如果绘图需要花费大量时间,您可以考虑:

  • 你能以某种方式优化绘图吗?
  • 你能减少点数吗?
    • 您或许可以绘制数据集的一小部分?
    • 您可以预处理绘图以减少点密度。屏幕通常有 2k-4k 的分辨率,所以如果你有一个包含更多点的折线图,用户无论如何都看不到它。

我的建议是废弃 obsolete BackgroundWorker,支持无限异步循环。下面的示例假设存在一个 Calculate 方法应该 运行 在后台线程上并且应该 return 一个计算的结果,以及一个 UpdateUI 方法应该 运行 在 UI 线程上并且应该使用这个结果。

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;
    }
}

本设计具有以下特点:

  1. CalculateUpdateUI 方法并行工作。
  2. 如果 Calculate 先完成,则等待 UpdateUI 完成后再开始下一次计算。
  3. 如果 UpdateUI 先完成,它会等待 Calculate 完成,然后再开始 UI.
  4. 的下一次更新
  5. 如果 CalculateUpdateUI 都在 100 毫秒内完成,则会施加额外的异步延迟,因此每秒不会发生超过 10 个循环。
  6. 无限循环可以通过取消可选的CancellationToken来终止。

上例中calculationResult变量的object类型只是为了演示。除非计算结果微不足道,否则您应该创建一个 class 或结构来存储在每个循环中更新 UI 所需的所有数据。通过消除所有全局状态,您可以最大限度地减少可能出错的次数。

用法示例:

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
}