System.IO.Ports.SerialPort和multithreading

我有一些常常需要从串行接口(例如COM1)读取数据的SerialPort代码。 但这似乎是CPU密集型的,如果用户移动窗口或者正在向窗口显示大量数据(例如通过串行线路接收的字节),那么通信就会搞砸。

考虑以下代码:

void port_DataReceived(object sender, SerialDataReceivedEventArgs e) { byte[] buffer = new byte[port.ReadBufferSize]; var count = 0; try { count = port.Read(buffer, 0, buffer.Length); } catch (Exception ex) { Console.Write(ex.ToString()); } if (count == 0) return; //Pass the data to the IDataCollector, if response != null an entire frame has been received var response = collector.Collect(buffer.GetSubByteArray(0, count)); if (response != null) { this.OnDataReceived(response); } 

需要收集代码,因为数据流是恒定的,并且必须分析数据(帧/分组)。

  port = new SerialPort(); //Port configuration code here... this.collector = dataCollector; //Event handlers port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived); port.Open(); 

如果没有用户交互并且没有任何东西被添加到窗口,这可以正常工作但是只要有交互通信就会搞砸了。 超时发生等……

例如,这会搞砸一切:

 Dispatcher.BeginInvoke(new Action(() => { var builder = new StringBuilder(); foreach (var r in data) { builder.AppendFormat("0x{0:X} ", r); } builder.Append("\n\n"); txtHexDump.AppendText(builder.ToString()); txtHexDump.ScrollToEnd(); }),System.Windows.Threading.DispatcherPriority.ContextIdle); }); 

但即使是对log4net的简单调用也会引发问题。

是否有任何最佳实践来优化SerialPort通信,或者有人能告诉我我做错了什么…

更新:

如果上述情况没有那么重要。 我做了一个非常简单(和愚蠢)的小例子:

 class Program { static void Main(string[] args) { var server = new BackgroundWorker(); server.DoWork += new DoWorkEventHandler(server_DoWork); server.RunWorkerAsync(); var port = new SerialPort(); port.PortName = "COM2"; port.Open(); string input = ""; Console.WriteLine("Client on COM2: {0}", Thread.CurrentThread.ManagedThreadId); while (input != "/quit") { input = Console.ReadLine(); if (input != "/quit") { var data = ASCIIEncoding.ASCII.GetBytes(input); port.Write(data, 0, data.Length); } } port.Close(); port.Dispose(); } static void server_DoWork(object sender, DoWorkEventArgs e) { Console.WriteLine("Listening on COM1: {0}", Thread.CurrentThread.ManagedThreadId); var port = new SerialPort(); port.PortName = "COM1"; port.Open(); port.ReceivedBytesThreshold = 15; port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived); } static void port_DataReceived(object sender, SerialDataReceivedEventArgs e) { var port = (SerialPort)sender; int count = 0; byte[] buffer = new byte[port.ReadBufferSize]; count = ((SerialPort)sender).Read(buffer, 0, buffer.Length); string echo = ASCIIEncoding.ASCII.GetString(buffer,0,count); Console.WriteLine("-->{1} {0}", echo, Thread.CurrentThread.ManagedThreadId); } } 

结果可能如下所示:

在COM2上监听COM1:6客户端:10这是我发送的一些示例数据—> 6这是我发送的一些示例数据

因此,从端口读取数据发生在主线程上….

这可能是造成我问题的部分原因吗?

您的最后一个结论是,事件在主线程上运行,对于Windows应用程序可能不适用。 不要在控制台中测试它。

调整它的正确方法是:

  • 设置足够大的缓冲区,尽管最小4096通常是Ok

  • 将ReceivedBytesThreshold设置为可容忍的高(并在Open() 之前执行)

  • 如果需要更多时间,请在Received事件中尽可能少地将数据传递给Queue或MemoryStream

我很惊讶没人抓到这个。 使用DataReceived事件时,SerialPort类使用自己的线程。 这意味着,例如,如果订阅者正在访问任何Form元素,则必须使用Invoke或BeginInvoke方法完成此操作。 否则,您最终会进行跨线程操作。 在.net的旧版本中,这会因为不可预测的行为而被忽视(取决于PC中的CPU核心),在以后的版本中,应该引发exception。

您应该重写port_DataReceived过程以读取数据,直到port.BytesToRead大于零,如下所示:

 private void port_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e) { var port = (SerialPort)sender; while (port.BytesToRead > 0) { int byte_count = port.BytesToRead; byte[] buffer = new byte[byte_count]; int read_count = port.Read(buffer, 0, byte_count); // PROCESS DATA HERE } } 

另外,我建议您只在过程port_DataReceived中的队列列表中插入数据,并在单独的线程中执行数据处理。

经典的解决方案是拥有FIFO缓冲区。 确保FIFO的大小足以处理任何有大量输入且处理器块被占用的关键情况。

你甚至可以有一个2缓冲系统:

 --->|Reader|-->FIFO-->|Processor|--->FIFO2--->|Displayer|