System.IO.Ports.SerialPort和多线程

时间:2009-06-10 04:03:26

标签: c# .net performance serial-port

我有一些常常需要从串行接口(例如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);
    }
}

结果可能如下所示:

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

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

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

4 个答案:

答案 0 :(得分:13)

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

答案 1 :(得分:4)

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

调整此方法的正确方法是:

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

  • 将ReceivedBytesThreshold设置为可容忍的高(并且之前打开())

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

答案 2 :(得分:2)

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

答案 3 :(得分:1)

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

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

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