【问题标题】:C# Drawing multiple charts in real time in a separate threadC#在单独的线程中实时绘制多个图表
【发布时间】:2015-10-14 21:26:33
【问题描述】:

我有一个读取数据并将其添加到缓冲区的采集设备。这是在一个单独的线程中完成的。一旦这些数据出队,我就会设置一个委托来引发 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();
    }

EDIT1: chChanels 是一个图表列表,其中每个元素对应一个图表。 公共列表 chChannels;

EDIT2 更改锁定后,所有图表都会更新。然而,每个图表的大量样本仍未更新。

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

【问题讨论】:

  • 什么是chChannels?恐怕除非您提供一个可以实际编译和测试的示例,否则任何尝试帮助您都很难走得很远。

标签: c# multithreading forms charts real-time


【解决方案1】:

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

  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 计时器中受益。

【讨论】:

  • 我更改了锁,现在图表总是更新。但是,仍然遗漏了许多价值。它可能与 DateTime.Now 有关吗?或者按照您的建议进行睡眠。
  • @nabrugir - 是的,很可能是 DateTime.Now 造成了麻烦。 DateTime.Now 在那个级别上是不可靠的。您可能想要使用高精度计时器(请参阅 System.Diagnostics.StopWatch),或者甚至重新评估您的时间戳策略。
【解决方案2】:

代码中有两个问题:

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,因此每 4ms 对应 1 个样本。 DateTime.Now 的精度在 15ms 左右,这使得这种方法不适用于 256Hz 的频率速率。但是,这对于较低的频率速率来说将是一个很好的方法。

正确的做法是在绘制数据时确定图表的 X 轴值。由于频率固定为 256Hz,因此很容易知道 Y 样本的 X 值。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-05-29
    • 2012-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-13
    • 1970-01-01
    相关资源
    最近更新 更多