C# 如何改进这个性能不佳、糟糕的串行端口代码?

C# 如何改进这个性能不佳、糟糕的串行端口代码?,c#,.net,performance,serial-port,C#,.net,Performance,Serial Port,我有一段很难看的串口代码,非常不稳定 void port_DataReceived(object sender, SerialDataReceivedEventArgs e) { Thread.Sleep(100); while (port.BytesToRead > 0) { var count = port.BytesToRead; byte[] buffer = new byte[count]; var re

我有一段很难看的串口代码,非常不稳定

void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    Thread.Sleep(100);
    while (port.BytesToRead > 0)
    {
        var count = port.BytesToRead;

        byte[] buffer = new byte[count];

        var read = port.Read(buffer, 0, count);

        if (DataEncapsulator != null)
            buffer = DataEncapsulator.UnWrap(buffer);


       var response = dataCollector.Collect(buffer);

       if (response != null)
       {
           this.OnDataReceived(response);
       }

       Thread.Sleep(100);
    }    
}
如果我删除其中一个Thread.Sleep(100)调用,代码将停止工作

当然,这确实会减慢速度,如果大量数据流进入, 除非我睡得更香,否则它也会停止工作。 (在纯死锁中停止工作)

请注意,数据封装器和数据收集器是组件 由MEF提供,但性能相当好

该类有一个Listen()方法,该方法启动后台工作程序 接收数据

public void Listen(IDataCollector dataCollector)
{
    this.dataCollector = dataCollector;
    BackgroundWorker worker = new BackgroundWorker();

    worker.DoWork += new DoWorkEventHandler(worker_DoWork);
    worker.RunWorkerAsync();
}

void worker_DoWork(object sender, DoWorkEventArgs e)
{
    port = new SerialPort();

    //Event handlers
    port.ReceivedBytesThreshold = 15;
    port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);

    ..... remainder of code ...
欢迎提出建议

更新: *只是简单介绍一下IDataCollector类的功能。 无法知道是否已发送所有字节的数据 在一次读取操作中读取。所以每次读取数据时,它都是 传递给DataCollector,当完成 已收到有效的协议消息。在这种情况下,它只是 检查同步字节、长度、crc和尾部字节。真正的工作 以后由其他类完成。 *

更新2: 我现在按照建议替换了代码,但仍然存在一些问题:

void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
        var count = port.BytesToRead;

        byte[] buffer = new byte[count];

        var read = port.Read(buffer, 0, count);

        if (DataEncapsulator != null)
            buffer = DataEncapsulator.UnWrap(buffer);

        var response = dataCollector.Collect(buffer);

        if (response != null)
        {
            this.OnDataReceived(response);
        }     
}
您可以看到,这在快速稳定的连接下运行良好。 但并非每次接收数据时都调用OnDataReceived。 (有关更多信息,请参阅MSDN文档)。因此,如果数据变得支离破碎 在事件数据丢失时,只读取一次

现在我想起了为什么我一开始就有这个循环,因为 如果连接速度慢或不稳定,它实际上必须读取多次

void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    Thread.Sleep(100);
    while (port.BytesToRead > 0)
    {
        var count = port.BytesToRead;

        byte[] buffer = new byte[count];

        var read = port.Read(buffer, 0, count);

        if (DataEncapsulator != null)
            buffer = DataEncapsulator.UnWrap(buffer);


       var response = dataCollector.Collect(buffer);

       if (response != null)
       {
           this.OnDataReceived(response);
       }

       Thread.Sleep(100);
    }    
}

显然,我不能回到while循环解决方案,那么我能做什么呢?

而且它可以非常简单

要么使用DataReceived处理程序,但没有循环,当然也没有Sleep(),读取准备好的数据并将其推送到某个地方(队列或内存流)

启动一个线程(BgWorker)并执行(阻塞)serialPort1.Read(…),然后再次推送或组装您获得的数据

编辑: 根据您发布的内容,我会说:删除eventhandler并读取Dowork()中的字节。这样做的好处是,您可以指定所需的数据量,只要它比ReadBufferSize小(很多)

Edit2,关于更新2: 在BgWorker中使用while循环仍然会更好,根本不使用事件。简单的方法是:

byte[] buffer = new byte[128];  // 128 = (average) size of a record
while(port.IsOpen && ! worker.CancelationPending)
{
   int count = port.Read(buffer, 0, 128);
   // proccess count bytes

}

现在,可能您的记录大小可变,您不想等待接下来的126个字节来完成一个。您可以通过减小缓冲区大小或设置读取超时来对此进行调整。要获得非常精细的粒度,可以使用port.ReadByte()。由于它从ReadBuffer中读取数据,所以速度并没有变慢。

我对原始的基于while的代码片段的第一个关注点是字节缓冲区的恒定内存分配。在这里放置一个“new”语句,专门用于.NET内存管理器,为缓冲区分配内存,同时获取在上一次迭代中分配的内存,并将其发送回未使用的池,以便最终进行垃圾收集。在一个相对紧密的循环中,这似乎需要做很多工作

我很好奇,如果在设计时以合理的大小(比如8K)创建此缓冲区,您将获得性能改进,这样您就不会有所有这些内存分配、释放和碎片。那会有帮助吗

private byte[] buffer = new byte[8192];

void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    Thread.Sleep(100);
    while (port.BytesToRead > 0)
    {
        var count = port.BytesToRead;

        var read = port.Read(buffer, 0, count);

        // ... more code   
    }
}
在循环的每次迭代中重新分配这个缓冲区的另一个问题是,如果缓冲区已经足够大,那么重新分配可能是不必要的。考虑以下事项:

  • 循环迭代1:接收100字节;分配100字节的缓冲区
  • 循环迭代2:接收75字节;分配75字节的缓冲区
在这种情况下,您实际上不需要重新分配缓冲区,因为在循环迭代1中分配的100字节缓冲区足以处理在循环迭代2中接收的75字节。无需销毁100字节的缓冲区并创建75字节的缓冲区。(当然,如果您只是静态地创建缓冲区并将其移出循环,这是没有意义的。)


另一方面,我可能建议DataReceived循环只关注数据的接收。我不确定这些MEF组件在做什么,但我怀疑它们的工作是否必须在数据接收回路中完成。是否可以将接收到的数据放在某种队列中,MEF组件可以在那里拾取它们?我对尽可能快地保持DataReceived循环感兴趣。也许可以将接收到的数据放在队列中,以便它可以直接返回到接收更多数据的工作中。您可能可以设置另一个线程来监视队列中的数据,并让MEF组件从队列中提取数据并从队列中执行工作。这可能需要更多的编码,但可能有助于数据接收环路尽可能响应。

如果要将数据写入文件,并且串行端口经常停止,这是一种简单的方法。如果可能,请将缓冲区设置为足够大,以容纳计划放入单个文件中的所有字节。然后在datareceived事件处理程序中编写代码,如下所示。然后,当您有机会将整个缓冲区写入一个文件时,如下所示。如果必须在串行端口读取缓冲区时读取缓冲区,请尝试使用缓冲流对象以避免死锁和争用情况

private byte[] buffer = new byte[8192]; 
var index = 0;
void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{    
    index += port.Read(buffer, index, port.BytesToRead);
} 

void WriteDataToFile()
{
    binaryWriter.Write(buffer, 0, index); 
    index = 0;
}

嗨,这就是DataCollector的工作。DataCollector收集所有字节,直到有完整的协议消息为止。每次调用collect时,它都将返回false,直到有完整的协议消息为止。我会在原来的问题中加入这一点。我必须马上研究你的建议我已经在编辑了。我建议在BgWorker中进行阻塞读取。我认为您已经解决了我的问题:)我将进行更多测试,但目前您的答案是正确的:DDoS不适用于慢速c