完全无法解释的 NullReferenceException
本文关键字:NullReferenceException 无法解释 | 更新日期: 2023-09-27 18:37:00
好的,所以我非常偶尔会在这行代码上得到一个 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)
所以我得到了一个 NRE,但整行代码中没有一个部分可能是空的。时期。(感觉到挫败感?这包括密钥,但这无论如何都不会抛出 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;
}
}
如果不看到代码所在的上下文,就很难确定。但根据您描述的症状,即非常零星的......不能说明的。。。有些东西是空的,不可能是...我强烈怀疑线程问题。例如,如果集合是静态的,并且可能由多个线程访问,则可能会发生第二个线程在第一个线程测试是否存在某些内容和访问该线程之间修改集合内容的情况(尽管这种情况很少发生)。
如果是这种情况,则必须使代码更加线程安全。可以使用锁定或并发集合来避免此问题。要使用 lock,您需要使用同步对象(而不是动态创建的新对象)。您还想寻找所有访问该收藏的地方,并用锁包围每个地方......仅查看集合的代码必须使用 lock 以及修改集合的代码。对于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
设置为 null。发生这些事情中的任何一件事都会导致最后一行抛出 NRE。
以下代码不是使代码线程安全的正确方法,但可以作为查看是否确实如此的快速方法,因为它会使这种情况(null 引用异常)的可能性降低:
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 计数。但是,如果这段代码大大减少了 NRE,那么你就很清楚发生了什么,并且你应该通过并在需要时使用锁正确使您的代码线程安全。更进一步地说,一个人需要更多地了解其他代码在做什么,尤其是修改_oracleTenantSettings的代码。
我的猜测是有另一个线程访问该属性。
修复它的一种快速方法是每次像这样访问它时都锁定它:
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
}
当心死锁。
我同意前面的答案,这可能是一个线程问题,但我有一些东西要补充。
这是一个快速而肮脏的测试,以确定它是否在线程。
设置一个重现错误的 scenerio(例如在一夜之间在恒定循环中在几 (10) 个线程中运行它)
将此属性应用于类
[同步]
您的类必须继承自 ContextBoundObject。
这会强制类的所有实例在单个线程上运行(速度较慢)。
重新运行测试。
如果问题消失,则说明存在线程问题。如果速度是一个问题,您需要返回并围绕接触该对象的代码进行所有锁定。如果您将所有内容转换为使用相关对象的属性,则可以锁定 getter 和 setter。
如果快速而肮脏的测试无法解决问题,则可能是其他原因。例如,如果您使用的是不安全的代码或不安全的dll(即用非.Net c ++编写的内容),则可能是内存损坏问题。
希望这有帮助。
下面是有关该属性的更多详细信息,包括从 ContextBoundObject 继承。
文档女士
代码示例:
// 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;
}
}
更新
我建议在代码中的某个地方,相等运算符或 == 已在其中一个相关对象上重载,并且当未正确检查 null(或失败)或某些内容返回为相等时发生故障。
检查 == 上的所有运算符重载,以查找在这种情况下使用的任何类对象并纠正。.
源语言
将逻辑更改为此,因为您要先检查无键...然后。。当存在有效键但(并且)其值为 null 时执行检查:
if ((_oracleTenantSettings.OraclePlanSettings.ContainsKey(_key) == false) ||
((_oracleTenantSettings.OraclePlanSettings.ContainsKey(_key)) &&
_oracleTenantSettings.OraclePlanSettings[_key] == null)))
如果您仔细考虑原始语句的逻辑流程,为什么它会间歇性地失败,它实际上是预期的。
编辑:让我解释一下,逐步遵循此逻辑
- 在原始 if 子句中,当它计算 (!ContainsKey(_key))表示当键不存在(true)时,它会更改为FALSE。
- 然后 Or 由于 #1 中的 False 而启动。它评估 OraclePlanSettings[_key] 但键不在那里,对吗?
- 因此,它执行代码以检查 null 是否存在无效键并引发异常。
只有打破我所展示的逻辑,才能不抛出。