C#在一个单独的线程

时间:2015-10-14 21:26:33

标签: c# multithreading forms charts real-time

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

在我的信号监视器中,当收到事件时,我想在多个图表中绘制数据(总共16个)。由于我有16个图表,而不是每次接收新数据时更新图表,我将它们添加到数据和时间戳的缓冲区中。通过读取存储在缓冲区中的数据和时间戳,每100ms在一个单独的线程中更新图表。但是,当我绘制数据时,某些图表会停止添加数据,而在所有图表中,不显示大量值。这是一个糟糕的方法吗?什么是更好的方法或我应该改变什么才能使它工作?

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

这就是我得到的 signal monitor (16 channels)

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

enter image description here

 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是一个图表列表,其中每个元素对应一个图表。      public List chChannels;

EDIT2 更改此锁定后,将更新所有图表。但是,每张图表的大量样本仍未更新。

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

enter image description here

2 个答案:

答案 0 :(得分:0)

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

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

    lock (_bufferRawData) {
        // Append data
        _bufferRawData.Add(e.rawData[sampleIdx]);
        _bufferXValues.Add(DateTime.Now);
    }
    
  2. 事实上,我还建议将锁定移出for-loop。锁定每次迭代是非常低效的:

        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 :(得分:0)

代码中存在两个问题:

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值。