我怎样才能改进这个性能不佳、糟糕的串行端口代码?
-
22-07-2019 - |
题
我有一段丑陋的串行端口代码,非常不稳定。
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) 调用,代码就会停止工作。
当然,这确实会减慢一切的速度,如果大量数据流,除非我使睡眠更大,否则它也会停止工作。(在纯粹的死锁中停止工作)
请注意,DataScaplutator和DataCollector是MEF提供的组件,但它们的性能非常好。
该类具有侦听方法,该方法启动了一个背景工人以接收数据。
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 类的作用。没有办法知道在单个读取操作中读取已发送的数据的所有字节。因此,每次读取数据时,都将传递给DataCollloctor,该数据已收到完整且有效的协议消息时返回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循环解决方案,那么我能做什么呢?
解决方案
我对原始基于 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处理程序,但没有一个循环,当然不睡觉(),读什么数据已准备就绪,地方推(到队列或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它不是真正任何慢。
如果您希望将数据写入文件和串行端口停止,每隔一段时间,这是一个简单的方法来做到这一点。如果可能的话让你的缓冲区大到足以容纳所有你计划在一个文件中的字节数。然后把代码写在你的DataReceived事件检索处理程序,如下图所示。然后,当你得到一个oportunity将整个缓冲区写入到文件如下图所示的是。如果您必须在串口朗读给您的缓冲区中读取从你的缓冲区,然后尝试使用缓冲流对象,以避免死锁和竞争条件。
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;
}