再次回到另一个串口问题。
在过去的两周里,我一直在改进我的代码,尝试读取两个串行设备。目前,该程序几乎所有数据(与之前的50%相比,大约99%),但我仍然缺少数据。
这是我的“主要”功能:
private Program()
{
port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);
port2.DataReceived += new SerialDataReceivedEventHandler(port2_DataReceived);
port.Open();
port2.Open();
Application.Run();
}
以下是我用于两个串口的代码:
public void port1IntoBuffer()
{
int messageLength = 96;
byte[] buffer = new byte[messageLength];
port.BaseStream.ReadAsync(buffer, 0, messageLength);
for (int i = 0; i < messageLength; i++)
{
if ((int)buffer[i] <= 48 && (int)buffer[i] > 0)
{
tickQ.Enqueue((new IdDate { Id = (int)buffer[i], Date = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") }));
}
}
if (!Locked)
{
Locked = true;
Thread readThread = new Thread(() => submitSQL());
readThread.Start();
}
}
我也将此代码复制到端口2。 messageLength只是一个用于测试的任意数字。至于我的输入,我期待1-48之间的整数值。 tickQ变量是一个ConcurrentQueue,我将两个端口排入(它后来出列并发送到SQL DB)。
如果有人能给我一些关于我做错的提示,我将不胜感激。
谢谢!
编辑1:
在阅读BlueStrat的建议后,我将我的sql提交代码更改为以下内容:
public void submitSQL()
{
lock (Locked)
{
int retryCount = 3;
bool success = false;
while (retryCount > 0 && !success)
{
try
{
IdDate tempData;
using (SqlConnection con = new SqlConnection())
using (SqlCommand cmd = new SqlCommand())
{
con.ConnectionString = "Data Source=xxxxxx;" +
"Initial Catalog=xxxxxx;" +
"User id=xxxxxx; Password=xxxxxx; Connection Timeout=0";
cmd.CommandText = "INSERT INTO XXXXX (submitTime, machineID) VALUES (@submitTIME, @machineId)";
cmd.Connection = con;
con.Open();
while (tickQ.TryDequeue(out tempData))
{
cmd.Parameters.Clear();
cmd.Parameters.AddWithValue("@machineId", (tempData.Id));
cmd.Parameters.AddWithValue("@submitTIME", tempData.Date);
cmd.ExecuteNonQuery();
}
con.Close();
}
}
catch (SqlException e)
{
Console.WriteLine(e.Message);
retryCount--;
}
finally
{
success = true;
}
}
}
}
现在我的问题是在10分钟之后,我对此程序的RAM使用率爆炸(从大约6MB增加到150MB),然后程序崩溃。
编辑2:根据每个人的建议,结果有所改善!我昨晚只错过了大约15,000个传输中的8个。我将尝试增加我的串行读取缓冲区,希望能够捕获更多的传输。
答案 0 :(得分:1)
我在您的代码中注意到了一些问题,第一个是我作为评论提出的问题。 'ReadAsync()'
函数调用是异步的,这意味着它将提前返回,而请求的数据则在后台获取。
通常,您可以通过在返回的await
上发出Task
来处理此问题,并在完全读取所请求的字节数后表示您希望代码恢复(不阻塞当前线程) 。
await
仅在C#5之后可用。如果您运行的是较低版本,则通常会安排延续,这是一种将在任务完成后运行的委托。但是,我认为只需切换到Read()
版本就可以了,线程肯定会在等待数据时阻塞,但至少你确定已经读取了预期的字节数。
我注意到的另一个问题是您的锁定代码依赖于布尔标志,它不是线程安全的。问题是检查Lock
标志状态以有条件地进入受保护块的代码和实际设置块内标志状态的代码不是原子的。 这意味着另一个线程可以输入您要保护的代码,而另一个线程也进入相同的代码块,但仍未设法将guard标志切换为true ,在这种情况下,两个线程都将运行相同的代码并可能产生意外的结果(不确定这种可能性是否与您丢失的数据问题有关)。
我真的不建议如何在不查看Lock
成员使用方式以及如何发布的情况下重写锁定保护代码。如果您发布与Lock
成员交互的其余代码段,我可能会提供一些建议。
编辑:( OP评论后的更多信息)
该标志的目的是阻止每个帖子发布到 数据库一下子(我一直遇到死锁错误,但我没有 认为这会导致我的数据丢失。)
有了这些信息,我认为你可能有另一个问题,可以解释缺失的数据。如果Flag为true会发生什么?你只是跳过启动向SQL服务器提交数据的线程,你永远不会重试,所以一旦方法退出,缓冲的数据就会丢失。抱歉,你不会丢失数据,因为你正在排队,你可能会失去的是实际耗尽数据接收队列所需的线程启动量。当您遇到数据丢失时,请检查您的tickQ
队列,我敢打赌它仍然有数据要处理,因为应该处理它的线程从未出于同样的原因。
在lock()
函数中添加submitSQL()
语句可以保护您免受这种可能性的影响。
从您的代码中完全删除Locked成员并尝试:
// Add this member to your class;
object SqlLock = new object();
void submitSQL() {
// add this at the start of your submitSQL() method
lock (SqlLock ) {
... the rest of your code
}
}
与当前代码的区别在于,如果另一个线程正在使用SQL实例,则调用线程将阻止AND WAIT 以使其在继续之前可用,而不是简单地丢弃刚刚读取的数据。不启动处理已排队数据的线程。
编辑:最后一条建议
我建议您在每次需要排空队列时不要创建新线程,而是可以依赖线程池(请参阅Task.Run()
)。使用池中的线程消除了手动创建线程的昂贵开销。
另一种可能性是创建一个连续专用于排空数据队列的单个线程。你可以使用AutoResetEvent
来协调生产者线程(排队的线程)和消费者线程(出队的线程)之间的工作。你的消费者线程进入一个调用AutoResetEvent.WaitOne()
的循环,在那里它将阻止;你的生产者线程将接收到的数据排队,而不是产生一个新线程,它调用AutoResetEvent.Set()
导致消费者线程唤醒并处理排队数据,一旦处理完毕,它再次阻塞,等待下一个数据批处理到了。如果这样做,请确保将线程标记为BackgroundThread
,因此如果线程正在等待数据,则不会阻止应用程序关闭。