对于下面的代码(.NET v4.0.30319),我将得到下面第二个延续中指示的空引用异常。
最有趣的是,这个问题只发生在有8GB RAM的机器上,但其他用户有16 8GB或更多的内存,而且他们没有报告任何问题,这是一个非常间歇性的问题,使我怀疑垃圾收集问题。
可以多次调用GetData(),因此_businessObjectTask的第一个延续将只被调用一次,因为从这一点开始已经填充了_businessObjects。
我想抛出Object reference not set to an instance of an object异常是因为
_businessObjectTask是空的,它不能从空任务继续。items变量在某种程度上是空的。日志文件( 748 )中的行号指向下面突出显示的一行,而不是指向上面#1而不是#2的lambda表达式。
任何帮助都将不胜感激。
编辑:这与What is a NullReferenceException, and how do I fix it?无关,因为这是对空引用的一个更基本的解释,而这却要复杂得多,也更加微妙。
异常
堆栈跟踪的详细信息(为简单起见,使用虚拟类名和命名空间名称进行编辑)
Object reference not set to an instance of an object.
at ApplicationNamespace.ClassName`1.<>c__DisplayClass4e.<GetData>b__44(Task`1 t) in e:\ClassName.cs:line 748
at System.Threading.Tasks.ContinuationTaskFromResultTask`1.InnerInvoke()
at System.Threading.Tasks.Task.Execute()码
private static IDictionary<string, IBusinessObject> _businessObjects;
private Task<IDictionary<string, IBusinessObject>> _businessObjectTask;
public Task GetData(IList<TBusinessItem> items))
{
Log.Info("Starting GetData()");
if (_businessObjects == null)
{
var businessObjectService = ServiceLocator.Current.GetInstance<IBusinessObjectService>();
_businessObjectTask = businessObjectService.GetData(CancellationToken.None)
.ContinueWith
(
t =>
{
_businessObjects = t.Result.ToDictionary(e => e.ItemId);
return _businessObjects;
},
CancellationToken.None,
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Current
);
}
var taskSetLEData = _businessObjectTask.ContinueWith // Line 748 in my code - "Object reference not set to an instance of an object." thrown here
(
task =>
{
items.ToList().ForEach
(
item =>
{
IBusinessObject businessObject;
_businessObjects.TryGetValue(item.Id, out businessObject);
item.BusinessObject = businessObject;
}
);
},
CancellationToken.None,
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Default
);
}决议:
因此,在使用了我从这个问题中学到的东西之后,我回到了原来的代码中,并将其全部解决了。
这个NRE的原因是因为_businessObjectTask是非静态的,因为_businessObjects是静态的。
这意味着_businessObjects在第一次调用GetData()时为null,然后将_businessObjectTask设置为非空。然后,当_businessObjectTask.ContinueWith被调用时,它是非空的,并且没有问题地继续。
但是,如果实例化了上面这个类的第二个实例,那么_businessObjects已经被填充,所以_businessObjectTask仍然是空的。然后,当_businessObjectTask.ContinueWith被调用时,会在_businessObjectTask上抛出一个NRE。
我本来可以选择几个选项,但最后我将_businessObjectTask移到了一个同步方法调用中,这意味着我不再需要使用延续,我只设置了一次_businessObjects。
发布于 2015-12-09 13:50:59
这是一个同步问题。
您假设_businessObjectTask总是在_businessObjects之前被分配。
然而,这是不能保证的。分配_businessObjects的延续可能是内联执行的,因此在businessObjectService.GetData(...).ContinueWith(...)返回之前执行。
// This assignment could happend AFTER the inner assignment.
_businessObjectTask = businessObjectService.GetData(CancellationToken.None)
.ContinueWith
(
t =>
{
// This assignment could happen BEFORE the outer assignment.
_businessObjects = t.Result.ToDictionary(e => e.ItemId); 因此,尽管_businessObjects _businessObjectTask 是空,但不为空是可能的。
如果并发线程当时将输入您的GetData方法,那么它显然不会输入
if (_businessObjects == null) // not entered because it's not null
{
...
}相反,...and继续使用
var taskSetLEData = _businessObjectTask.ContinueWith // line 748...which将导致空引用异常,因为_businessObjectTask为空。
下面是如何简化代码并解决这个同步问题的方法:
private Lazy<Task<IDictionary<string, IBusinessObject>>> _lazyTask =
new Lazy<Task<IDictionary<string, IBusinessObject>>>(FetchBusinessObjects);
private static async Task<IDictionary<string, IBusinessObject>> FetchBusinessObjects()
{
var businessObjectService = ServiceLocator.Current.GetInstance<IBusinessObjectService>();
return await businessObjectService.GetData(CancellationToken.None).ToDictionary(e => e.ItemId);
}
public async Task GetData(IList<TBusinessItem> items)
{
Log.Info("Starting GetData()");
var businessObjects = await _lazyTask.Value;
items.ToList().ForEach
(
item =>
{
IBusinessObject businessObject;
businessObjects.TryGetValue(item.Id, out businessObject);
item.BusinessObject = businessObject;
}
);
}备注:
Lazy<T>确保只调用一次业务对象服务(这个类的每个实例,不管它是什么)。async/await简化代码。_lazyTask声明为静态的。在您的代码中,似乎在静态/非静态字段之间出现了混淆。我不知道哪一个适合你。https://stackoverflow.com/questions/34177032
复制相似问题