我有一段丑陋的串行端口代码,它非常不稳定。
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) 调用,代码将停止工作。
当然,这确实会减慢速度,如果有大量数据流进来, 除非我让 sleep 更大,否则它也会停止工作。 (在纯死锁中停止工作)
请注意 DataEncapsulator 和 DataCollector 是组件 由 MEF 提供,但它们的性能相当不错。
该类有一个 Listen() 方法,它启动一个后台 worker 来 接收数据。
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 类的作用。 无法知道是否已发送数据的所有字节 在单个读取操作中读取。所以每次读取数据都是 传递给 DataColllector,它在完成时返回 true 并且 已收到有效的协议(protocol)消息。在这种情况下,这里只是 检查同步字节、长度、crc 和尾字节。真正的工作 稍后由其他类(class)完成。 *
更新 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 文档)。所以如果数据变得支离 splinter 并且您只在事件数据丢失时读取一次。
现在我记得为什么我首先要有循环,因为 如果连接缓慢或不稳定,它实际上必须多次读取。
显然我不能回到 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 组件从那里获取数据并从那里完成它们的工作。这可能需要更多编码,但它可能有助于数据接收循环尽可能地响应。
关于c# - 我怎样才能改进这个性能不佳、糟糕的串行端口代码?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1233958/