易失性和锁定不起作用 - C# 4.0
本文关键字:不起作用 锁定 易失性 | 更新日期: 2023-09-27 18:33:46
我有一个从串行读取数据的类,阈值很高(1 字节(。
我有一个变量来存储来自串行端口的所有数据:_dataReceived。
private volatile string _dataReceived;
我正在使用 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);
}
处理它,包括将其从变量中删除并处理答案。方法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;
}
...
}
这在 99.9% 的情况下有效。
但有时我会在这条线上得到一个 ArgumentOutOfRangeException:
cmd = _dataReceived.Substring(0, idx);
现在,我的问题是:我的变量是易失性的,这意味着我总是访问真正的值而不是缓存。我很确定我一直(快速(引发此 DataReceived 事件,但我正在使用 lock 语句来防止任何其他线程更改此值。如果没有字符串中的换行符,就无法让这段代码(子字符串(运行。而且这个 IndexOf 不可能从字符串中返回索引。
那么......这到底是怎么回事?
测试任何东西都非常困难,因为它每个月只发生一次,但我们欣赏任何关于实际发生的事情的理论。
感谢您的任何建议!
volatile 关键字从来都不是同步问题的解决方案。 该错误清晰可见,您做了一个硬性假设,即在 ParseAnswers(( 使用字符串并完成运行之前,DataReceived 事件处理程序不会再次执行。 这是一厢情愿的想法,当事件处理程序再次触发并在 ParseAnswers(( 解析字符串时替换字符串时,您的代码会崩溃。 使用易失性实际上会使您的代码更容易崩溃:) 您还应该注意到数据丢失,当 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);
}
}
...
}
请注意,我已经删除了volatile
修饰符(这是多余的(,并且我还在该方法中同步了对_dataReceived
_port_DataReceived
的访问(非常非常重要(