Wag*_*ior 0 c# multithreading locking serial-port volatile
我有一个类从一个串行读取数据,具有高阈值(1个字节).
我有一个变量存储来自串口的所有数据:_dataReceived.
private volatile string _dataReceived;
Run Code Online (Sandbox Code Playgroud)
我正在使用DataReceived事件来存储这些数据,然后我开始一个动作来处理它.
private void _port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
string newData = _port.ReadExisting();
_dataReceived += newData;
new Action(() =>
{
Debug("Data received: {0}", newData);
ParseAnswers();
}).BeginInvoke(null, null);
}
Run Code Online (Sandbox Code Playgroud)
处理它,包括从变量中删除它并处理答案.ParseAnswers方法如下所示:
private void ParseAnswers()
{
string cmd = null;
int idx = -1;
lock (_dataReceived)
{
idx = _dataReceived.IndexOf(Environment.NewLine);
if (idx != -1)
{
cmd = _dataReceived.Substring(0, idx);
_dataReceived = _dataReceived.Substring(idx + 2);
}
else
return;
}
...
}
Run Code Online (Sandbox Code Playgroud)
这有99.9%的时间.
但有时我会在这一行得到一个ArgumentOutOfRangeException:
cmd = _dataReceived.Substring(0, idx);
Run Code Online (Sandbox Code Playgroud)
现在,我的问题是:我的变量是volatile,这意味着我总是访问真正的值而不是缓存.我很确定我的DataReceived事件一直在增加(快速),但是我使用lock语句来阻止任何其他线程更改此值.如果没有在字符串中使用NewLine,就无法运行这段代码(子字符串).而且这个IndexOf无法从字符串中返回索引.
那么......这里到底发生了什么?
测试任何东西都非常困难,因为它只是每个月发生一次,但我们欣赏任何关于实际发生的事情的理论.
感谢您的任何建议!
的易失性的关键字是从不同步问题的解决方案.该错误清晰可见,您做了一个很难的假设,即在ParseAnswers()消耗字符串并完成运行之前,DataReceived事件处理程序不会再次执行.这是一厢情愿的想法,当事件处理程序再次触发时,您的代码崩溃并在ParseAnswers()解析时替换字符串.使用volatile实际上使你的代码更容易崩溃:)你也应该注意到数据丢失,当ParseAnswers()运行得太晚时会发生这种情况.
解决方案非常简单,给ParseAnswers()一个参数.传递字符串.
使用Invoke()而不是BeginInvoke()也是一种解决方案,SerialPort确保DataReceived在执行时不能被触发.但它非常危险,当你试图调用Close()时容易使程序死锁.
这就是我们建议在私有只读字段上使用锁的原因.
dataReceived在_port_DataReceived执行ParseAnswers上一个事件时,您可以通过方法中的另一个线程更改(因为访问未同步).
所以会发生什么,现在两个线程竞争lock(_dataReceived),它们都被允许,因为它们都使用锁对象的不同引用.
记住lock语句适用于引用,_dataReceived += newData;更改引用,因此可以自由地允许另一个线程进入关键区域(因为它现在使用不同的字符串实例).
简单的解决方法是:
private string _dataReceived;
private readonly object padLock = new object();
private void _port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
string newData = _port.ReadExisting();
lock (padLock)
{
_dataReceived += newData;
}
new Action(() =>
{
Debug("Data received: {0}", newData);
ParseAnswers();
}).BeginInvoke(null, null);
}
private void ParseAnswers()
{
...
lock (padLock)
{
idx = _dataReceived.IndexOf(Environment.NewLine);
if (idx != -1)
{
cmd = _dataReceived.Substring(0, idx);
_dataReceived = _dataReceived.Substring(idx + 2);
}
}
...
}
Run Code Online (Sandbox Code Playgroud)
请注意,我已经删除了volatile修改(这是多余的),还我同步访问_dataReceived的_port_DataReceived方法也(非常非常重要)