C#在单独的线程中实时绘制多个图表

C# Drawing multiple charts in real time in a separate thread

我有一个采集设备,可以读取数据并将其添加到缓冲区。这是在一个单独的线程中完成的。一旦这些数据出队,我设置了一个引发事件 OnDataRead() 的委托。

在我的信号监视器中,当接收到事件时,我想在多个图表(总共 16 个)中绘制数据。因为我有 16 个图表,而不是每次收到新数据时都更新图表,我将它们添加到数据和时间戳的缓冲区中。通过读取存储在缓冲区中的数据和时间戳,每 100 毫秒在一个单独的线程中更新图表。然而,当我绘制数据时,一些图表停止添加数据并且在所有图表中,很多值都没有显示。这是一个不好的方法吗?什么是更好的方法,或者我应该改变什么才能让它发挥作用?

我有 256 个样本/秒 * 16 个通道。

这就是我得到的

这是我所期望的,但对于所有 16 个频道

 public void OnDataRead(object source, DataEventArgs e)
    {
         if ((e.rawData.Length > 0) && (!_shouldStop))
            {
                for (int sampleIdx = 0; sampleIdx < e.rawData.Length; sampleIdx++)
                {
                    lock (_bufferRawData)
                        // Append data
                        _bufferRawData.Add(e.rawData[sampleIdx]);

                    lock (_bufferXValues)
                        _bufferXValues.Add(DateTime.Now);

                }
    }

 private void AddDataThreadLoop()
        {
            while (!_shouldStop)
            {
                chChannels[1].Invoke(addDataDel);

                Thread.Sleep(100); //sleeps for 100ms
            }
        }

 private void AddData()
        {

            // Copy data stored in lists to arrays
            if (_bufferRawData.Count > 0)
            {
                float[] rawData;
                lock (_bufferRawData)
                {
                    rawData = _bufferRawData.ToArray();
                    _bufferRawData.Clear();
                }
                DateTime[] xValues;
                lock (_bufferXValues)
                {
                    xValues = _bufferXValues.ToArray();
                    _bufferXValues.Clear();
                }

                // Add new data points for the selected channel chart
                int channelIdx = 0; 

                for (int sampleIdx = 0; sampleIdx < rawData.Length -1; sampleIdx++)
                {
                    // Calculate the channel where the smaple corersponds
                    channelIdx = sampleIdx % (_numChannels + 1);

                   foreach (Series ptSeries in chChannels[channelIdx].Series)
                            // Add new datapoint to the corresponding chart (x, y, chartIndex, seriesIndex)
                            AddNewPoint(xValues[sampleIdx], rawData[sampleIdx], channelIdx, ptSeries);

                }
            }
        }

public void AddNewPoint(DateTime timeStamp, float yValue, int chartIDx, System.Windows.Forms.DataVisualization.Charting.Series ptSeries)
    {

        //Add datapoint
        ptSeries.Points.AddXY(timeStamp.ToOADate(), yValue);

        // Remove old datapoints if needed
        double removeBefore = timeStamp.AddSeconds((double)(8) * (-1)).ToOADate();
        while (ptSeries.Points[0].XValue < removeBefore)
        {
            ptSeries.Points.RemoveAt(0);
        }

        // Modify minimum and maximum for new samples
        chChannels[chartIDx].ChartAreas[0].AxisX.Minimum = ptSeries.Points[0].XValue;
        chChannels[chartIDx].ChartAreas[0].AxisX.Maximum = DateTime.FromOADate(ptSeries.Points[0].XValue).AddSeconds(10).ToOADate();
        chChannels[chartIDx].ChartAreas[0].AxisY.Maximum = _yMax;
        chChannels[chartIDx].ChartAreas[0].AxisY.Minimum = -_yMax;

        chChannels[chartIDx].Invalidate();
    }

private void btnPlay_Click(object sender, EventArgs e)
{
     //Create thread
     //define a thread to add values into chart
     ThreadStart addDataThreadObj = new ThreadStart(AddDataThreadLoop);
     addDataRunner = new Thread(addDataThreadObj);
     addDataDel += new AddDataDelegate(AddData);

     //Start thread
     addDataRunner.Start();
    }

编辑 1: chChanels 是图表列表,其中每个元素对应于其中一个图表。 public 列出 chChannels;

EDIT2 为此更改锁定后,所有图表都会更新。但是每个图表的很多样本仍然没有更新。

 lock (_bufferRawData) {
        for (int sampleIdx = 0; sampleIdx < e.rawData.Length; sampleIdx++)
        {
           // Append data
           _bufferRawData.Add(e.rawData[sampleIdx]);
           _bufferXValues.Add(DateTime.Now);
        }
    }

很难说这些是否是您唯一的问题,但至少这对我来说很突出:

  1. 您正在为时间戳和原始数据缓冲区使用两个单独的锁。这意味着缓冲区可能不代表同一系列的数据。您可能希望使用一个锁来访问两个缓冲区:

    lock (_bufferRawData) {
        // Append data
        _bufferRawData.Add(e.rawData[sampleIdx]);
        _bufferXValues.Add(DateTime.Now);
    }
    

事实上,我还建议将锁定移出 for 循环。每次迭代都锁定是非常低效的:

    lock (_bufferRawData) {
        for (int sampleIdx = 0; sampleIdx < e.rawData.Length; sampleIdx++)
        {
           // Append data
           _bufferRawData.Add(e.rawData[sampleIdx]);
           _bufferXValues.Add(DateTime.Now);
        }
    }
  1. 您正在做的另一件事可以改进(但不一定解释为什么数据看起来很奇怪)是您在安排更新的线程中休眠。您可能应该将其替换为定期触发并进行更新的计时器。由于您在 UI 线程上调用更新,因此您可能会受益于使用 UI 计时器。

代码中有两个问题:

1) 以低效的方式使用锁。我把两把单锁换成一把普通锁。正如其中一个答案所指出的,我还为所有迭代锁定它,而不是为每个迭代都锁定它。应该是这样

 lock (_bufferRawData) {
        for (int sampleIdx = 0; sampleIdx < e.rawData.Length; sampleIdx++)
        {
           // Append data
           _bufferRawData.Add(e.rawData[sampleIdx]);
           _bufferXValues.Add(DateTime.Now);
        }
    }

2) 我生成的时间戳与收集数据时的时间戳不一致。因此,多个 Y 样本具有相同或相似的时间戳。

一种解决方案是为每个样本生成准确的时间戳。这应该在获取样本时完成。但是,由于频率为 256Hz,因此对应于每 4 毫秒 1 个样本。 DateTime.Now 的精度在 15ms 左右,这使得这种方法不适用于 256Hz 的频率。但是,对于较低的频率,这将是一个很好的方法。

正确的方法是在绘制数据时确定图形的 X 轴值。由于频率固定在256Hz,所以很容易知道Y样本的X值。