3

运行我的多线程应用程序时,我收到 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”不为空,并且我还尝试将 foreach 更改为 for,以防 IEnumerator 做一些有趣的事情,但异常发生在同一行。

关于如何进一步调查的任何想法?

更新:一些响应者提出了与确保记录器不为空有关的可能解决方案。需要明确的是,在异常开始发生后添加了日志语句用于调试目的。

4

6 回答 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;

问题原来是我使用“某物”变量作为临时强引用,直到 Update 方法获得永久引用。编译器在发布模式下优化掉“something = m_object.Add();” 分配,因为“某物”在再次分配之前不会使用。这允许 ISomething 被垃圾收集,因此当我尝试访问它时它不再存在于 m_collection 中。

我所要做的就是确保在调用 Update 之前我持有一个强有力的参考。

我怀疑这对任何人是否有用,但如果有人好奇,我不想让这个问题悬而未决。

于 2009-06-11T21:00:35.110 回答
2

它记录“十”的事实会让我先看看:

  • 曾经被logger分配...这可能会以某种方式变为空
  • 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扩展方法,并且异常会清楚地表明它到底在哪里出错。也许一旦修好就改回来……或者离开它。

于 2009-06-10T22:15:44.603 回答
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);
        }

这个特殊问题可以通过 int? foreach 中的 i 或 Object i 或使用通用容器。

于 2009-06-10T22:26:59.260 回答
0

同意 Fredrik 的意见——需要更多细节。一个可能开始寻找的地方:您提到多线程应用程序以及发布中发生的错误但不是调试。您可能会遇到多个线程访问相同对象引用的时间问题。

无论如何,我也可能会放一个:

Debug.Assert(classItemCollection != null);

就在循环迭代之前。它不会在发布模式下帮助您,但如果(何时?)它发生在调试中,它可能会帮助您发现问题。

于 2009-06-10T22:27:30.740 回答
0

我会寻找将记录器或其依赖项之一设置为空的代码。是否有记录器的属性,当设置为空时,可能会触发这个?发布模式有时会加快应用程序的执行速度,这可能会揭示由调试模式和/或调试器的性能损失所掩盖的同步问题。

于 2009-06-10T22:30:46.913 回答
0

“十一”没有被记录的事实让我相信 logger 在调用之前被设置为 null 。你能把它包装在一个 try/catch 中,看看它是否碰到了块的 catch 部分?发生这种情况时,也许您可​​以插入 MessageBox.Show 或将某些内容写入已知文件。

于 2009-06-10T22:36:41.690 回答