完全无法解释的NullReferenceException

时间:2012-06-13 19:52:12

标签: c# .net exception clr nullreferenceexception

好的,所以我偶尔在这行代码上得到一个NullReferenceException:

if (!_oracleTenantSettings.OraclePlanSettings.ContainsKey(_key) || _oracleTenantSettings.OraclePlanSettings[_key] == null)

和/或这一行:

_oraclePlanSettings = _oracleTenantSettings.OraclePlanSettings[_key];

其中OraclePlanSettings是一个SortedList,它不能为null,因为有问题的代码被包围:

 if (_oracleTenantSettings.OraclePlanSettings != null && _oracleTenantSettings.OraclePlanSettings.Count > 0)

所以我得到了一个N​​RE,但整个代码行中没有一部分可能永远都是null。期。 (感觉沮丧吗?)这包括钥匙,但无论如何也不会抛出NRE。我不明白。是否可能VS只是错放了CLR异常?如果是这样,哪里是一个开始寻找的好地方?

堆栈跟踪只是一个单行:

 at company.product.Mvc.OracleSettingsStoreCache.VerifyValueInCacheOrInsert[T](T& returnVal, SettingsType settingType, String tenantId, String planId, String pageMnemonic, String processId, String transcationType, String language, String country, String wapTransactionType, String wapCodeGroup, String wapLoanReasons, String palleteType, Boolean isInsert, Object _cacheValue) in blahblahblah.OracleSettingsStoreCache.cs:line 290

以下是整个代码块:

if (!string.IsNullOrEmpty(tenantId) && (!IsWacMode() || (IsWacMode() && settingType == OracleSettingsType.SettingsType.FetchWAPInvestmentTransfer)) && _useCache != "false")
                {
                    tenantId = tenantId.ToUpper().Trim();

                    _oracleTenantSettings = null;

                    if (_oracleCacheManager.Contains(_cacheKey))
                        _oracleTenantSettings = _oracleCacheManager.Get<OracleTenantSetting>(_cacheKey);

                    if (_oracleTenantSettings != null)
                    {
                        if (_oracleTenantSettings.OraclePlanSettings != null && _oracleTenantSettings.OraclePlanSettings.Count > 0)
                        {
                            _key = language + "_" + country + "_" + tenantId;
           ***LINE 290***   if (!_oracleTenantSettings.OraclePlanSettings.ContainsKey(_key) || _oracleTenantSettings.OraclePlanSettings[_key] == null)
                            {
                                _objectMissing = TypeOfObjectMissing.TenantObjectDoesNotExist;
                            }
                        }

4 个答案:

答案 0 :(得分:4)

如果没有看到代码所在的上下文,就很难确定。但是基于你描述的症状,即非常零星的...莫名其妙......某些东西是无效的......我强烈怀疑是一个线程问题。例如,如果集合是静态的并且可能被多个线程访问,则第二个线程在第一个线程测试时是否存在某些内容以及何时访问时,会发生第二个线程修改集合内容的情况(尽管很少发生机会定时)那东西。

如果是这种情况,您必须使代码更加线程安全。您可以使用锁定或并发集合来避免此问题。要使用锁定,您需要使用同步对象(而不是动态创建的新对象)。您还需要搜索访问该集合的所有位置,并用锁定环绕每个位置......只需查看集合的代码必须使用锁定以及修改集合的代码。这是SO答案的一个重要主题,所以我建议你使用这个非常好的资源:

http://www.albahari.com/threading/

以下是在这种情况下你可以获得NRE的方法:

thread 1 checks if entry exists in SortedList myList for _key="hello" 
gets true
thread 1 checks if entry for _key="hello" is non-null
gets true
thread 2 sets myList["hello"] = null
thread 1 executes myList["hello"].Something() and gets NRE.

根据您对帖子的编辑,似乎在这些行中

if (_oracleTenantSettings != null) {
    if (_oracleTenantSettings.OraclePlanSettings != null && _oracleTenantSettings.OraclePlanSettings.Count > 0) {
        _key = language + "_" + country + "_" + tenantId;
         if (!_oracleTenantSettings.OraclePlanSettings.ContainsKey(_key) || _oracleTenantSettings.OraclePlanSettings[_key] == null)

如果NRE出现在最后一行,那么在执行第一行或第二行之后,另一个线程可以将_oracleTenantSettings_oracleTenantSettings.OraclePlanSettings设置为空。发生这些事情中的任何一个都会导致最后一行抛出NRE。

以下代码不是使代码线程安全的正确方法,但可以作为一种快速方法来查看是否确实如此,因为它会使这种情况(空引用异常)不太可能:

var oracleTS = _oracleTenantSettings;
if (oracleTS != null) {
    var planSettings = oracleTS.OraclePlanSettings;
    if ((planSettings != null) && (planSettings.Count > 0)) {
        _key = language + "_" + country + "_" + tenantId;
        if (!planSettings.ContainsKey(_key) || planSettings[_key] == null)

请注意,最后一行仍然可能存在与线程相关的其他问题,例如条件和第二部分的第一部分之间的另一个线程正在删除的键,或者测试后的planSettings Count更改。但是如果这段代码大大减少了NRE,那么你就可以很好地了解发生了什么,并且你应该通过锁定来正确地使你的代码线程安全。更进一步说,一个人需要更多地了解其他代码正在做什么,特别是修改_oracleTenantSettings的代码。

答案 1 :(得分:3)

我的猜测是有另一个线程访问该属性。

一种快速解决问题的方法就是锁定它每次访问时都会这样:

var oraclePlanSettings = _oracleTenantSettings.OraclePlanSettings;
lock (oraclePlanSettings)
{
    // from now on you can safely access your cached reference "oraclePlanSettings"
    if (oraclePlanSettings != null && oraclePlanSettings.Count > 0)
        _oraclePlanSettings = oraclePlanSettings[_key]; // ... blabla
}

小心死锁。

答案 2 :(得分:1)

我同意以前的答案,这可能是一个线程问题,但我有一些补充。

这是一个快速而肮脏的测试,用于确定它是否正在穿线。

设置一个再现错误的scenerio(比如在一个恒定循环中在几(10)个线程中运行它)

将此属性应用于您的班级

[同步]

您的类必须从ContextBoundObject继承。

这会强制类的所有实例在单个线程上运行(方式较慢)。

重新开始测试。

如果问题消失,则会出现线程问题。如果速度是一个问题,你需要返回并对触及该对象的代码进行所有锁定。如果您将所有内容转换为使用相关对象的属性,则可以锁定getter和setter。

如果快速而肮脏的测试无法解决问题,那么它可能是另一回事。例如,如果您使用不安全的代码或不安全的dll(即用非.Net c ++编写的东西),则可能是内存损坏问题。

希望这有帮助。

以下是有关该属性的更多详细信息,包括从ContextBoundObject继承。

Ms Docs

代码示例:

// Context-bound type with the Synchronization context attribute.
[Synchronization()]
public class SampleSynchronized : ContextBoundObject {

    // A method that does some work, and returns the square of the given number.
    public int Square(int i)  {

        Console.Write("The hash of the thread executing ");
        Console.WriteLine("SampleSynchronized.Square is: {0}", 
                             Thread.CurrentThread.GetHashCode());
        return i*i;
    }
}

答案 3 :(得分:-1)

更新

我建议在代码中的某个位置,Equality运算符或==已经在其中一个相关对象上重载,并且当没有正确检查null(或者失败)或者返回等值时,会发生失败当它不是。

检查==对于在这种情况下使用的任何类对象的所有操作符重载并纠正..

原始

将逻辑更改为此,因为您要首先检查无密钥...然后检查有效密钥但是()其值为空:< / p>

 if ((_oracleTenantSettings.OraclePlanSettings.ContainsKey(_key) == false) || 
     ((_oracleTenantSettings.OraclePlanSettings.ContainsKey(_key)) && 
       _oracleTenantSettings.OraclePlanSettings[_key] == null)))

如果您通过原始语句的逻辑流程思考它为什么间歇性地失败,它实际上是预期的。 : - )

修改: 让我解释一下,按步骤

来遵循这个逻辑
  1. 在原始if子句中,当它计算(!ContainsKey(_key))时,意味着当键不存在(true)时,它会变为FALSE。
  2. 然后由于#1中的False而开始。它评估OraclePlanSettings [_key] BUT 密钥不对吗?
  3. 因此它执行代码以检查无效密钥的null并抛出异常。
  4. 只有突破我所显示的逻辑,才能抛出这种情况。