易失性和锁定不起作用 - 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 不可能从字符串中返回索引。

那么......这到底是怎么回事?

测试任何东西都非常困难,因为它每个月只发生一次,但我们欣赏任何关于实际发生的事情的理论。

感谢您的任何建议!

易失性和锁定不起作用 - C# 4.0

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的访问(非常非常重要(