当函数返回时出现NullReferenceException异常

3

当我在多线程应用程序中运行Release模式时,我遇到了NullReferenceException,但是在调试器之外运行时才会出现。堆栈跟踪被记录下来,它总是指向相同的函数调用。我在函数中放置了几个日志记录语句,以尝试确定它能够执行到哪一步,每个语句都被记录下来,包括函数的最后一行。有趣的是,当NullReferenceException发生时,函数调用后的语句不会被记录下来:

    // ...
    logger.Log( "one" );  // logged
    Update( false );
    logger.Log( "eleven" );  // not logged when exception occurs
}

private void Update( bool condition )
{
    logger.Log( "one" );  // logged
    // ...  
    logger.Log( "ten" );  // logged, even when exception occurs
}

异常不会在每次调用函数时都发生。是否有可能堆栈在执行函数之前或期间被损坏,导致返回地址丢失,从而出现空引用?我认为在.NET下不可能发生这种事情,但我想更奇怪的事情也已经发生过了。
我尝试用函数的内容替换对函数的调用,这样一切都在内联中发生,然后异常发生在一个看起来像这样的行上:
foreach ( ClassItem item in classItemCollection )

我通过日志验证了"classItemCollection"不是null,我也尝试将foreach更改为for,以防IEnumerator做了一些奇怪的事情,但异常发生在同一行。

有什么想法可以进一步调查这个问题吗?

更新:几位回答者建议可能的解决方案与确保记录器不为null有关。 请注意,日志记录语句是在异常开始发生后添加的,用于调试目的。


可以在此处包含函数的代码吗?这可能会使情况更容易被发现。 - Fredrik Mörk
在 C# 中,不可能发生这种情况。除非你调用了不安全的代码。 - Euro Micelli
1
“eleven”没有被记录 - 也许它只是没有被刷新? - Marc Gravell
6个回答

5
我发现了我的空引用。像Fredrik和micahtan建议的那样,我没有提供足够的信息让社区找到解决方案,所以我想我应该发布我发现的内容,以便解决这个问题。
以下是发生的情况的表示:
ISomething something = null;

//...

// the Add method returns a strong reference to an ISomething
// that it creates.  m_object holds a weak reference, so when
// "this" no longer has a strong reference, the ISomething can
// be garbage collected.
something = m_object.Add( index );

// the Update method looks at the ISomethings held by m_object.
// it obtains strong references to any that have been added,
// and puts them in m_collection;
Update( false );

// m_collection should hold the strong reference created by 
// the Update method.
// the null reference exception occurred here
something = m_collection[ index ];

return something;

问题出在我将“something”变量作为临时强引用,直到Update方法获得了一个永久的引用。在Release模式下,编译器优化掉了“something = m_object.Add();”赋值语句,因为直到它被再次赋值之前,“something”都没有被使用。这导致ISomething被垃圾回收,因此当我尝试访问它时,它已经不存在于m_collection中。
我所要做的就是确保我拥有一个强引用,直到调用Update之后。
我怀疑这对任何人都没有用处,但以防万一有人好奇,我不想留下这个问题无解。

2
“它记录了“ten”的事实使我首先看:”
  • 是否曾分配logger……这可能会变成null
  • Log本身是否存在错误
“没有足够的上下文很难确定是哪个问题,但这是我调查的方式。您还可以在某个地方添加一个简单的null测试;作为一种机智的方法,您可以将Log方法重命名为其他名称,并添加扩展方法:”
[Conditional("TRACE")]
public static void Log(this YourLoggerType logger, string message) {
    if(logger==null) {
       throw new ArgumentNullException("logger",
            "logger was null, logging " + message);
    } else {
       try {
           logger.LogCore(message); // the old method
       } catch (Exception ex) {
           throw new InvalidOperationException(
                "logger failed, logging " + message, ex);
       }
    }
}

您现有的代码应该调用新的Log扩展方法,异常将清楚地显示出它在哪里出错。也许在修复后改回来...或者可能保留。


我在异常开始出现后添加了日志调用以尝试弄清楚发生了什么。某些日志语句的放置似乎可以解决问题,这似乎表明这与时间有关。 - Jeff Hillman

0

你是否从多个线程修改classItemCollection?如果你在另一个线程中改变了集合,可能会导致迭代器失效,从而导致异常。你可能需要使用锁来保护访问。

编辑:你能否发布有关ClassItem和classItemCollection类型的更多信息?

另一种可能性是ClassItem是值类型,classItemCollection是通用集合,某种方式向集合中添加了null。以下内容将引发NullReferenceException:

        ArrayList list=new ArrayList();

        list.Add(1);
        list.Add(2);
        list.Add(null);
        list.Add(4);

        foreach (int i in list)
        {
            System.Diagnostics.Debug.WriteLine(i);
        }

这个特定问题可以通过在 foreach 中使用 int? i 或 Object i,或者使用泛型容器来解决。


classItemCollection 是函数内部的本地变量。 - Jeff Hillman

0

同意Fredrik的观点——需要更多细节。也许可以从以下方面开始查找:您提到了多线程应用程序,并且错误发生在发布版本中而不是调试版本中。您可能会遇到多个线程访问相同对象引用的时间问题。

无论如何,我也可能会添加:

Debug.Assert(classItemCollection != null);

在循环迭代之前加上。这在发布模式下不会有帮助,但如果问题在调试时发生,它可能会帮助你捕捉到问题。


我在foreach周围加了一个“if(classItemCollection!= null)”,但异常仍然发生。这个集合不可能为null,它可以是空的,但不是null。 - Jeff Hillman
如果集合不为null,我不确定为什么你会在那行代码上得到NRE。我很好奇问题是什么...(如果你想调试这样的事情,这是不好的吗?) - micahtan

0

我会寻找设置logger或其依赖项为null的代码。是否有logger的属性,当设置为null时,可能会触发这种情况?发布模式有时会加速应用程序的执行,这可能会揭示在调试模式和/或调试器的性能惩罚掩盖下的同步问题。


0

“eleven”未被记录的事实让我相信在该调用之前,记录器已被设置为null。你能否尝试包装它以捕获异常,并查看是否触发了块的catch部分?或许当这种情况发生时,你可以插入MessageBox.Show或将某些内容写入已知文件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接