收到的串口数据处理速度太慢

时间:2014-08-19 19:54:08

标签: c# datagridview datatable serial-port invoke

我正在以115200的波特率从arduino读取数据。数据以字符串的形式出现在其自己的行中,格式为:<ID,Name/Data>

我认为我的代码存在的问题是它没有足够快地处理传入数据,并且强制传入数据等待旧数据的处理。

传入的字符串被拆分为三个单独的类别(ID,名称,数据),并添加到名为dtFromGrid的数据表中,该数据表绑定到dataGridView1

是否有任何关于如何提高代码性能的错误或建议?处理函数的单独线程是否比BeginInvoke更好?

serialPort1.DataReceived += new SerialDataReceivedEventHandler(serialPort1_DataReceived);

    private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
    {
        string inData = serialPort1.ReadLine();
        if (PauseButton.Text == "Pause" && inData.StartsWith("<"))
        {
            try
            {
                this.BeginInvoke(new SetGridDeleg(DoUpdate), new object[] {inData});
            }
            catch
            {
            }
        }
    }

    private void DoUpdate(string inData)   //inData passed in so that Serial port read only once
    {
        if (dtFromGrid == null)
        {
            dtFromGrid = new DataTable();
            dtFromGrid.Columns.Add("Time", typeof(String));
            dtFromGrid.Columns.Add("ID", typeof(String));
            dtFromGrid.Columns.Add("Name", typeof(String));
            dtFromGrid.Columns.Add("Data", typeof(String));
        }

        DataRow dr = dtFromGrid.NewRow();
        TimeSpan ts = stopWatch.Elapsed;
        dr["Time"] = String.Format("{0:00}:{1:00}:{2:00}.{3:000}",
        ts.Hours, ts.Minutes, ts.Seconds,
        ts.Milliseconds);
        dr["ID"] = inData.Split(new char[] { '<', ',' })[1];
        dr["Name"] = inData.Split(new char[] { ',', '/' })[1];
        dr["Data"] = inData.Split(new char[] { '/', '>' })[1];
        dtFromGrid.Rows.InsertAt(dr, 0);

        //Replace old data with new data if ID's are the same to showo list of only newest data per each ID
        if (NewestButton.Text == "Chronological")
        {
            for (int i = 1; i < dataGridView1.Rows.Count; i++)
            {
                if (dtFromGrid.Rows[i].ItemArray[1].ToString() == dtFromGrid.Rows[0].ItemArray[1].ToString())
                {
                    dtFromGrid.Rows[i].Delete();
                    break;
                }
            }
        }

        //Keep a maximum of 50 rows of data
        if (dtFromGrid.Rows.Count == 51)
        {
            dtFromGrid.Rows[50].Delete();
        }
        dtFromGrid.AcceptChanges();
        dataGridView1.DataSource = dtFromGrid;

        //keep focus of dataGridView on top row
        dataGridView1.CurrentCell = dataGridView1.Rows[0].Cells[0];

        // add newest row to a logfile if the user has set one
        if (logFile != "")
        {
            using (StreamWriter sw = File.AppendText(logFile))
            {
                DataRow row = dtFromGrid.Rows[0];
                object[] array = row.ItemArray;
                int col = 0;
                for (col = 0; col < array.Length - 1; col++)
                {
                    sw.Write(array[col].ToString() + "\t|\t");
                }
                sw.Write(array[col].ToString());
                sw.WriteLine();
                sw.Close();
            }
        }
    }

更新

我现在正在使用一个单独的线程,但我在调用该线程内部时遇到错误。我随机得到多个错误,但最常见的是“索引超出范围”。我的调用代码如下: this.Invoke((MethodInvoker) delegate { dtFromGrid.AcceptChanges(); dataGridView1.DataSource = dtFromGrid; dataGridView1.CurrentCell = dataGridView1.Rows[0].Cells[0]; });

3 个答案:

答案 0 :(得分:0)

将数据存储在队列中并将工作卸载到辅助线程。这只适用于平均来说,您能够以其进入的速率处理数据。否则,当您落后时,队列的大小将继续增长。

首先,从Queue<T>周围的包装器开始,它将允许一个线程写入队列,另一个线程以线程安全的方式从中读取。此外,允许读者线程阻止等待数据。

public class ThreadedQueue<T>
{
    private readonly Queue<T> _queue = new Queue<T>();
    private readonly ManualResetEvent _notEmptyEvt = new ManualResetEvent(false);

    public WaitHandle WaitHandle { get { return _notEmptyEvt; } }

    public void Enqueue(T obj)
    {
        lock (_queue)
        {
            _queue.Enqueue(obj);
            _notEmptyEvt.Set();
        }
    }

    public T Dequeue()
    {
        _notEmptyEvt.WaitOne(Timeout.Infinite);
        lock (_queue)
        {
            var result = _queue.Dequeue();
            if (_queue.Count == 0)
                _notEmptyEvt.Reset();
            return result;
        }
    }
}

在串口处理程序中,将数据写入队列:

private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
    string inData = serialPort1.ReadLine();
    if (PauseButton.Text == "Pause" && inData.StartsWith("<"))
    {
        _queue.Enqueue(inData);
    }
}

在辅助线程中,从队列中读取并调用GUI线程:

private void ThreadProc()
{
    while (true)
    {
        string inData = _queue.Dequeue();
        this.Invoke(new SetGridDeleg(DoUpdate), new object[] {inData});
    }
}

启动辅助线程,如下所示:

Thread th = new Thread(ThreadProc);
th.IsBackground = true;
th.Start();

当然,您需要创建队列实例:

ThreadedQueue<string> _queue = new ThreadedQueue<string>();

答案 1 :(得分:0)

我通常设计一个SerialService类来管理SerialPort。下面是SerialService类的简单版本。

SerialService类的作用是尽可能快地读取串行缓冲区。这会清除缓冲区并防止出现任何串口错误。然后将此原始数据传递给解析器。

性能的诀窍在于解析器。 YourParser也应该快速将原始数据格式化为您期望的字符串。解析数据后,您可以使用回调或事件。通过回调或事件,您的解析器将继续解析新到达的数据。 YourParse现在是一个可测试的类。

一旦从解析器的回调中获得了良好的数据,就可以使用BeginInvoke将数据发送到主线程,然后你可以在其中显示它。

如果您不在主UI线程中,并且尝试从另一个线程更新UI,则会遇到交叉问题。

祝你好运。

class Program
{
    private static YourDataParser _parser;
    static void Main(string[] args)
    {
        _parser = new YourDataParser();
        var serial = new SerialService("COM1");
        serial.DataReceived += serial_DataReceived;
    }

    static void serial_DataReceived(object sender, DataReceivedEventArgs e)
    {
        _parser.HandleTheData(e.Data, good =>
        {
            // here is your good data
            // This is not the main thread invoke your UI from here with the good data
            // Use BeginInvoke to invoke the main thread
        });
    }
}

public class YourDataParser
{
    private List<byte> _buffer = new List<byte>(); 

    public void HandleTheData(byte[] rawdata, Action<string> goodData)
    {
        _buffer.AddRange(rawdata);

        foreach (var b in _buffer)
        {
            var thechar = (char) b;

            // handle your raw data... like look for the character '<'
            // or look for the end of line this would be CR (0x0D) LF (0x0A) 
            // you can reference the ASCII table for the characters byte values
        }

        // and return the good data 
        var data = "your good data after parsing it";
        goodData(data);
    }
}

public class DataReceivedEventArgs : EventArgs
{
    public DataReceivedEventArgs(byte[] data)
    {
        Data = data;
    }

    public byte[] Data { get; private set; }
}

class SerialService
{
    public event EventHandler<DataReceivedEventArgs> DataReceived;

    private SerialPort _port;
    public SerialService(string comm)
    {
        _port = new SerialPort(comm)
        {
            // YOUR OTHER SETTINGS HERE...
            ReceivedBytesThreshold = 1 // I think is better to increase this number if you know the minimum number of bytes that will arrive at the serial port's buffer
        };

        // Note that the ReceivedBytesThreshold is set to 1. 
        // That means that the port_DataReceived event will fire with a minimun of 1 byte in the serial buffer
        _port.DataReceived += port_DataReceived;
    }

    void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        if (e.EventType != SerialData.Chars) return;

        while (_port.IsOpen & _port.BytesToRead != 0)
        {
            // important to get all the bytes off the buffer
            var size = _port.BytesToRead; 
            var buffer = new byte[size];

            var sizeRead = _port.Read(buffer, 0, size);

            OnDataReceived(buffer);
        }

    }

    protected virtual void OnDataReceived(byte[] data)
    {
        var ev = DataReceived;
        if (ev != null) ev(this, new DataReceivedEventArgs(data));
    }
}

答案 2 :(得分:-1)

正如您所说,您的代码正在减慢数据接收速度。 您可以通过将数据排队到队列列表来解决您的问题,后台进程将逐个处理此列表。 另一种方法是在接收每个数据批次时创建一个新线程 示例(第二种方法)

private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
    {
       string inData = serialPort1.ReadLine();
       System.Threading.Thread T = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(ProcessData));
       T.Start(inData);
}
public void ProcessData(Object data)
{
....
}