【问题标题】:System.IO.Ports.SerialPort and MultithreadingSystem.IO.Ports.SerialPort 和多线程
【发布时间】:2010-11-01 16:45:26
【问题描述】:

我有一些 SerialPort 代码经常需要从串行接口(例如 COM1)读取数据。但这似乎是 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);
    }
}

结果可能如下所示:

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

所以从端口读取数据发生在主线程上......

这可能是导致我的问题的部分原因吗?

【问题讨论】:

  • 最后一段代码在哪里调用? “数据”变量从何而来?
  • 当 IDataCollector 引发事件以指示它收集了一整帧数据时,将调用上面的代码。它在 WPF 窗口中。但是,即使您在 IDataTransportServer 或 IDataCollector 实现的代码中添加 log.Warn(".....") 语句,SerialCommunication 也会被搞砸......“数据”只是我们从串行线路中读取的内容: buffer.GetSubByteArray(0, count)
  • 几乎任何代码都会导致串行通信出错...

标签: c# .net performance serial-port


【解决方案1】:

我很惊讶没有人发现这一点。 SerialPort 类在使用 DataReceived 事件时使用自己的线程。这意味着,例如,如果订阅者正在访问任何 Form 元素,则必须使用 Invoke 或 BeginInvoke 方法来访问。否则,您最终会进行跨线程操作。在旧版本的 .net 中,这会随着不可预知的行为(取决于 PC 中的 CPU 内核)而被忽视,而在更高版本中,应该会引发异常。

【讨论】:

  • +1,虽然我不确定这是否能回答问题,但它是过去让我有点痛苦的重要信息。
【解决方案2】:

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

调整这个的正确方法是:

  • 设置足够大的缓冲区,虽然通常最小4096就可以了

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

  • 在 Received 事件中尽可能少做,通过 如果您需要更多时间,可以将数据发送到队列或 MemoryStream

【讨论】:

    【解决方案3】:

    您应该重写 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 的队列列表中插入数据,并在单独的线程中执行数据处理。

    【讨论】:

    • 等待,在 port_DataReceived() 函数或 port_DataReceived() 内部没有被调用?我在原始帖子中没有写几件重要的事情: - port_DataReceived 是回调函数,它在自己的线程中执行。每当有一些数据写入 COM 端口时都会调用它。但是如果新数据写入 COM 端口并且您仍然没有从先前的调用返回,即在函数内部处理数据,那么对 port_DataReceived 函数的调用可能会失败,因此在离开函数之前检查 port.BytesToRead 属性很重要.
    【解决方案4】:

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

    您甚至可以拥有一个 2 缓冲区系统:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-07-22
      • 1970-01-01
      • 2016-12-19
      • 1970-01-01
      • 2016-09-14
      • 1970-01-01
      • 2013-03-01
      相关资源
      最近更新 更多