我怎样才能改进这个性能不佳、糟糕的串行端口代码?

发布于 2024-08-01 21:01:33 字数 2347 浏览 2 评论 0原文

我有一段丑陋的串行端口代码,非常不稳定。

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) 调用,代码就会停止工作。

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

请注意 DataEncapsulator 和 DataCollector 是组件 由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,当完成且返回 true 时 已收到有效的协议消息。 在这种情况下,它只是 检查同步字节、长度、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 文档)。 所以如果数据变得碎片化 并且您只在事件数据丢失时读取一次。

现在我记得为什么我首先要循环,因为 如果连接速度慢或不稳定,它实际上必须多次读取。

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

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.

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.

The class has a Listen() method which starts a background worker to
receive data.

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.
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. In this case here it just
checks for a sync byte, length , crc and tail byte. The real work
is done later by other classes.
*

Update 2:
I replaced the code now as suggested, but still there is something wrong:

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.
(See the MSDN docs for more). 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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

我要还你自由 2024-08-08 21:01:33

我对原始基于 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 组件从那里获取数据并从那里开始工作。 这可能需要更多编码,但它可能有助于数据接收循环尽可能响应。

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. 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. 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; allocate buffer of 100 bytes
  • Loop Iteration 2: 75 bytes received; allocate buffer of 75 bytes

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. (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. 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. Is it possible for the received data to be put on some sort of queue and the MEF components can pick them up there? I am interested in keeping the DataReceived loop as speedy as possible. 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. That may be more coding, but it may help the data reception loop be as responsive as possible.

情深已缘浅 2024-08-08 21:01:33

如果您想将数据写入文件并且串行端口经常停止,这是一种简单的方法。 如果可能,请使缓冲区足够大以容纳您计划放入单个文件中的所有字节。 然后在 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;
}

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. 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;
}
音盲 2024-08-08 21:01:33

它可以如此简单...

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

或者

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

编辑:

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

Edit2,关于Update2:

在 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 个字节进入来完成记录。 您可以通过减小缓冲区大小或设置 ReadTimeOut 来对此进行调整。 要获得非常细粒度的信息,您可以使用 port.ReadByte()。 由于是从 ReadBuffer 读取数据,所以速度并没有变慢。

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),

or

Start a Thread (BgWorker) and do a (blocking) serialPort1.Read(...), and again, push or assemble the data you get.

Edit:

From what you posted I would say: drop the eventhandler and just Read the bytes inside Dowork(). That has the benefit you can specify how much data you want, as long as it is (a lot) smaller than the ReadBufferSize.

Edit2, regarding Update2:

You will still be much better of with a while loop inside a BgWorker, not using the event at all. 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. You can tune this by reducing the buffer size or set a ReadTimeOut. To get very fine-grained you could use port.ReadByte(). Since that reads from the ReadBuffer it's not really any slower.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文