简体   繁体   English

如何改善这个表现不佳,可怕的串口代码?

[英]How can I improve this underperforming, terrible Serial Port code?

I have an ugly piece of Serial Port code which is very unstable. 我有一个丑陋的串口代码,非常不稳定。

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);
    }    
}

If I remove either Thread.Sleep(100) calls the code stops working. 如果我删除Thread.Sleep(100)调用代码停止工作。

Of course this really slows things down and if lots of data streams in, it stops working as well unless I make the sleep even bigger. 当然,这确实会减慢速度,如果有大量数据输入,它会停止工作,除非我让睡眠更大。 (Stops working as in pure deadlock) (停止在纯死锁中工作)

Please note the DataEncapsulator and DataCollector are components provided by MEF, but their performance is quite good. 请注意,DataEncapsulator和DataCollector是MEF提供的组件,但它们的性能非常好。

The class has a Listen() method which starts a background worker to receive data. 该类有一个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 ...

Suggestions are welcome! 欢迎提出建议!

Update: *Just a quick note about what the IDataCollector classes do. 更新: *只是快速说明IDataCollector类的功能。 There is no way to know if all bytes of the data that has been sent are read in a single read operation. 无法知道在单次读取操作中是否读取了已发送数据的所有字节。 So everytime data is read it is passed to the DataColllector which returns true when a complete and valid protocol message has been received. 因此,每次读取数据时,它都会传递给DataColllector,当收到完整有效的协议消息时,DataColllector返回true。 In this case here it just checks for a sync byte, length , crc and tail byte. 在这种情况下,它只检查同步字节,长度,crc和尾字节。 The real work is done later by other classes. 实际工作稍后由其他课程完成。 * *

Update 2: I replaced the code now as suggested, but still there is something wrong: 更新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);
        }     
}

You see this works fine with a fast and stable connection. 您可以看到这种方法可以快速稳定地连接。 But OnDataReceived is NOT called every time data is received. 但是每次收到数据时都不会调用OnDataReceived。 (See the MSDN docs for more). (有关更多信息,请参阅MSDN文档)。 So if the data gets fragmented and you only read once within the event data gets lost. 因此,如果数据碎片化,您只在事件中读取一次数据丢失。

And now I remember why I had the loop in the first place, because it actually does have to read multiple times if the connection is slow or unstable. 现在我记得为什么我首先有循环,因为如果连接缓慢或不稳定,它实际上必须多次读取。

Obviously I can't go back to the while loop solution, so what can I do? 显然我不能回到while循环解决方案,所以我该怎么办?

My first concern with the original while-based code fragment is the constant allocation of memory for the byte buffer. 我对原始基于时间的代码片段的首要关注是字节缓冲区的内存的常量分配。 Putting a "new" statement here specifically going to the .NET memory manager to allocate memory for the buffer, while taking the memory allocated in the last iteration and sending it back into the unused pool for eventual garbage collection. 将“new”语句专门放到.NET内存管理器中为缓冲区分配内存,同时获取上次迭代中分配的内存并将其发送回未使用的池以进行最终的垃圾回收。 That seems like an awful lot of work to do in a relatively tight loop. 在相对紧凑的循环中,这似乎是一项非常艰巨的工作。

I am curious as to the performance improvement you would gain by creating this buffer at design-time with a reasonable size, say 8K, so you don't have all of this memory allocation and deallocation and fragmentation. 我很好奇通过在设计时以合理的大小(比如8K)创建此缓冲区可以获得的性能提升,因此您没有所有这些内存分配和释放和碎片。 Would that help? 那会有帮助吗?

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   
    }
}

My other concern with re-allocating this buffer on every iteration of the loop is that the reallocation may be unnecessary if the buffer is already large enough. 我在循环的每次迭代中重新分配此缓冲区的另一个问题是,如果缓冲区已足够大,则可能不需要重新分配。 Consider the following: 考虑以下:

  • Loop Iteration 1: 100 bytes received; 循环迭代1:接收100个字节; allocate buffer of 100 bytes 分配100字节的缓冲区
  • Loop Iteration 2: 75 bytes received; 循环迭代2:接收75个字节; allocate buffer of 75 bytes 分配75字节的缓冲区

In this scenario, you don't really need to re-allocate the buffer, because the buffer of 100 bytes allocated in Loop Iteration 1 is more than enough to handle the 75 bytes received in Loop Iteration 2. There is no need to destroy the 100 byte buffer and create a 75 byte buffer. 在这种情况下,您实际上不需要重新分配缓冲区,因为在循环迭代1中分配的100字节缓冲区足以处理在循环迭代2中接收的75字节。无需销毁100字节缓冲区并创建一个75字节缓冲区。 (This is moot, of course, if you just statically create the buffer and move it out of the loop altogether.) (当然,如果您只是静态地创建缓冲区并将其完全移出循环,这是没有实际意义的。)

On another tangent, I might suggest that the DataReceived loop concern itself only with the reception of the data. 在另一个切线上,我可能会建议DataReceived循环只关注数据的接收。 I am not sure what those MEF components are doing, but I question if their work has to be done in the data reception loop. 我不确定那些MEF组件正在做什么,但我怀疑他们的工作是否必须在数据接收循环中完成。 Is it possible for the received data to be put on some sort of queue and the MEF components can pick them up there? 是否有可能将接收到的数据放在某种队列中,MEF组件可以在那里接收它们? I am interested in keeping the DataReceived loop as speedy as possible. 我有兴趣尽可能快地保持DataReceived循环。 Perhaps the received data can be put on a queue so that it can go right back to work receiving more data. 也许收到的数据可以放在一个队列中,这样它就可以回到工作区接收更多的数据。 You can set up another thread, perhaps, to watch for data arriving on the queue and have the MEF components pick up the data from there and do their work from there. 您可以设置另一个线程来监视到达队列的数据,并让MEF组件从那里获取数据并从那里开始工作。 That may be more coding, but it may help the data reception loop be as responsive as possible. 这可能是更多编码,但它可能有助于数据接收循环尽可能地响应。

And it can be so simple... 它可以这么简单......

Either you use DataReceived handler but without a loop and certainly without Sleep(), read what data is ready and push it somewhere (to a Queue or MemoryStream), 要么使用DataReceived处理程序但没有循环,当然没有Sleep(),请读取准备好的数据并将其推送到某处(到Queue或MemoryStream),

or 要么

Start a Thread (BgWorker) and do a (blocking) serialPort1.Read(...), and again, push or assemble the data you get. 启动一个线程(BgWorker)并执行(阻塞)serialPort1.Read(...),然后再次推送或汇编您获得的数据。

Edit: 编辑:

From what you posted I would say: drop the eventhandler and just Read the bytes inside Dowork(). 从你发布的内容我会说:删除eventhandler并只读取Dowork()里面的字节。 That has the benefit you can specify how much data you want, as long as it is (a lot) smaller than the ReadBufferSize. 这样做的好处是,您可以指定所需的数据量,只要它比ReadBufferSize小(很多)。

Edit2, regarding Update2: Edit2,关于Update2:

You will still be much better of with a while loop inside a BgWorker, not using the event at all. 在BgWorker中使用while循环仍然会好得多,而根本不使用该事件。 The simple way: 简单的方法:

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

}

Now maybe your records are variable-sized and you don't don't want to wait for the next 126 bytes to come in to complete one. 现在,您的记录可能是可变大小的,并且您不希望等待接下来的126个字节来完成一个。 You can tune this by reducing the buffer size or set a ReadTimeOut. 您可以通过减小缓冲区大小或设置ReadTimeOut来调整此值。 To get very fine-grained you could use port.ReadByte(). 为了获得非常细粒度,您可以使用port.ReadByte()。 Since that reads from the ReadBuffer it's not really any slower. 因为从ReadBuffer读取它并不是真的慢。

If you want to write the data to a file and the serial port stops every so often this is a simple way to do it. 如果要将数据写入文件并且串口经常停止,这是一种简单的方法。 If possible make your buffer large enough to hold all the bytes that you plan to put in a single file. 如果可能,使缓冲区足够大,以容纳您计划放在单个文件中的所有字节。 Then write the code in your datareceived event handler as shown below. 然后在datareceived事件处理程序中编写代码,如下所示。 Then when you get an oportunity write the whole buffer to a file as shown below that. 然后,当你得到机会时,将整个缓冲区写入文件,如下所示。 If you must read FROM your buffer while the serial port is reading TO your buffer then try using a buffered stream object to avoid deadlocks and race conditions. 如果您必须在串行端口读取缓冲区时读取缓冲区,请尝试使用缓冲流对象以避免死锁和竞争条件。

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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM