我有一段很难看的串口代码非常不稳定。
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()方法,它启动后台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 已收到有效的协议消息。在这种情况下,它只是 检查同步字节,长度,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循环解决方案,所以我该怎么办?
答案 0 :(得分:2)
我对原始基于时间的代码片段的第一个顾虑是字节缓冲区的内存的常量分配。将“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字节缓冲区足以处理在循环迭代2中接收的75字节。没有必要销毁100字节缓冲区并创建75字节缓冲区。 (当然,如果您只是静态地创建缓冲区并将其完全移出循环,这是没有意义的。)
在另一个切线上,我可能会建议DataReceived循环仅关注数据的接收。我不确定那些MEF组件正在做什么,但我怀疑他们的工作是否必须在数据接收循环中完成。是否有可能将接收到的数据放在某种队列中,MEF组件可以在那里接收它们?我有兴趣尽可能快地保持DataReceived循环。也许收到的数据可以放在一个队列中,这样它就可以回到工作区接收更多的数据。您可以设置另一个线程来监视到达队列的数据,并让MEF组件从那里获取数据并从那里开始工作。这可能是更多编码,但它可能有助于数据接收循环尽可能响应。
答案 1 :(得分:1)
它可以这么简单......
要么使用DataReceived处理程序但没有循环,当然没有Sleep(),请读取准备好的数据并将其推送到某处(到Queue或MemoryStream),
或
启动线程(BgWorker)并执行(阻塞)serialPort1.Read(...),然后再次推送或汇编您获得的数据。
从你发布的内容我会说:删除eventhandler并只读取Dowork()里面的字节。只要它(比很多)小于ReadBufferSize,就可以指定你想要多少数据。
在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读取它并不是真的慢。
答案 2 :(得分:0)
如果要将数据写入文件并且串口经常停止,这是一种简单的方法。如果可能,使缓冲区足够大,以容纳您计划放在单个文件中的所有字节。然后在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;
}