空引用-任务ContinueWith() [英] Null reference - Task ContinueWith()

查看:73
本文介绍了空引用-任务ContinueWith()的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

对于下面的代码(.NET v4.0.30319),我在下面的第二个续篇中得到了空引用异常.

最有趣的是,此问题仅发生在具有8GB RAM的计算机上,但其他用户的内存为16GB以上,并且他们没有报告任何问题,这是一个非常断断续续的问题,使我怀疑是垃圾回收问题.

可以多次调用 GetData(),因此_businessObjectTask的第一个延续仅被调用一次,因为从该点开始将填充_businessObjects.

我认为未抛出 Object引用未设置为对象实例异常是因为

  1. _businessObjectTask 为空,无法从空任务继续.
  2. 作为参数传递的
  3. items 变量以某种方式为空

我的日志文件(748)中的行号指向下面突出显示的行,而不是指向上面的#1而不是#2的lambda表达式.我在Visual Studio中玩过, businessObjectTask.ContinueWith()之后的每一行都被认为是不同的,即,如果在lambda表达式中为空引用,它将为748提供不同的行号./p>

任何帮助将不胜感激.

这与什么是NullReferenceException有关,并且我该如何解决?因为这是对null引用的更基本的解释,而这却更加复杂和微妙.

例外

堆栈跟踪的详细信息(为简化起见,使用伪类和名称空间名称进行了编辑)

 对象引用未设置为对象的实例.在ApplicationNamespace.ClassName`1处.<> c__DisplayClass4e.< GetData> b__44(Task`1 t)在e:\ ClassName.cs:line 748中在System.Threading.Tasks.ContinuationTaskFromResultTask`1.InnerInvoke()处在System.Threading.Tasks.Task.Execute() 

代码

 私有静态IDictionary< string,IBusinessObject>_businessObjects;私有Task< IDictionary<字符串,IBusinessObject>>_businessObjectTask;公共任务GetData(IList< TBusinessItem>项)){Log.Info("Starting GetData()");如果(_businessObjects == null){var businessObjectService = ServiceLocator.Current.GetInstance< IBusinessObjectService>();_businessObjectTask = businessObjectService.GetData(CancellationToken.None).继续(t =>{_businessObjects = t.Result.ToDictionary(e => e.ItemId);返回_businessObjects;},CancellationToken.None,TaskContinuationOptions.OnlyOnRanToCompletion,TaskScheduler.Current);}var taskSetLEData = _businessObjectTask.ContinueWith//我的代码中的第748行-对象引用未设置为对象的实例."扔在这里(任务=>{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 首次为null时将调用 GetData(),然后将 _businessObjectTask 设置为非null.然后,当调用 _businessObjectTask.ContinueWith 时,它为非null,并且继续进行而不会出现问题.

但是,如果实例化了该类的第二个实例,则已经填充了 _businessObjects ,因此 _businessObjectTask 保持为空.然后,当调用 _businessObjectTask.ContinueWith 时,将抛出 _businessObjectTask 上的NRE.

本来可以选择几个选项,但是最终我将 _businessObjectTask 删除到一个同步方法调用中,这意味着我不再需要使用延续性了,因此我设置了 _businessObjects一次.

解决方案

这是一个同步问题.

您假设始终在 _businessObjects 之前分配 _businessObjectTask .

但是,这不能保证.分配 _businessObjects 的延续可能在内联中执行,因此在之前 businessObjectService.GetData(...).ContinueWith(...)返回.

 //此分配可能在内部分配之后发生._businessObjectTask = businessObjectService.GetData(CancellationToken.None).继续(t =>{//此分配可能在外部分配之前进行._businessObjects = t.Result.ToDictionary(e => e.ItemId); 

因此,尽管 _businessObjectTask 为空,但 _businessObjects 可能不为空.

如果并发线程当时会输入您的 GetData 方法,则显然不会输入

  if(_businessObjects == null)//未输入,因为它不为null{...} 

...然后继续

  var taskSetLEData = _businessObjectTask.ContinueWith//第748行 

...这将导致空引用异常,因为 _businessObjectTask 为空.


以下是您可以简化代码并解决此同步问题的方法:

  private Lazy< Task< IDictionary< string,IBusinessObject>>_lazyTask =新的Lazy< Task< IDictionary< string,IBusinessObject>>>(FetchBusinessObjects);私有静态异步Task< IDictionary<字符串,IBusinessObject>>FetchBusinessObjects(){var businessObjectService = ServiceLocator.Current.GetInstance< IBusinessObjectService>();返回等待状态businessObjectService.GetData(CancellationToken.None).ToDictionary(e => e.ItemId);}公共异步任务GetData(IList< TBusinessItem>项){Log.Info("Starting GetData()");var businessObjects =等待_lazyTask.Value;items.ToList().ForEach(item =>{IBusinessObject businessObject;businessObjects.TryGetValue(item.Id,out businessObject);item.BusinessObject = businessObject;});} 

注意:

  • 使用 Lazy< T> 确保业务对象服务仅被调用一次(对于此类的每个实例,无论它是什么).

  • 使用 async / await 简化代码.

  • 您可能需要考虑将 _lazyTask 声明为静态.在您的代码中,静态/非静态字段之间似乎混杂在一起.我不知道哪一个适合您.

For the following piece of code (.NET v4.0.30319) I am getting a null reference exception indicated below in the second continuation.

Most interestingly this issue has only occurred in machines with 8GB RAM but other users have 16GB and more and they haven't reported any issue, and it is a very intermittent issue which leads me to suspect a garbage collection issue.

The GetData() can be called multiple times so the first continuation of _businessObjectTask will only be called once as _businessObjects will have been populated from that point forward.

I imagine the Object reference not set to an instance of an object exception is being thrown because either

  1. _businessObjectTask is null and it can't continue from a null task.
  2. items variable which is passed in as a parameter is null somehow

The line number in my log file (748) points to the one highlighted below and not the lambda expression which points to #1 above instead of #2. I've played around in Visual Studio and each of the lines following businessObjectTask.ContinueWith() are considered different i.e. if it was a null reference within the lambda expression it would give a different line number to 748

Any help would be greatly appreciated.

Edit: This isn't related to What is a NullReferenceException, and how do I fix it? because that is a much more basic explanation of null reference whereas this is much more complicated and subtle.

Exception

Full details of the stack trace (edited for simplicity with dummy class and namespace names)

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()

Code

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
    );
}

Resolution:

So having used what I've learned from this question I went back to the original code and figured it all out.

Turns out the reason for this NRE was because the _businessObjectTask is non-static where as the _businessObjects is static.

This means that _businessObjects is null the first time GetData() is called which then sets _businessObjectTask to non-null. Then when _businessObjectTask.ContinueWith gets called it is non-null and continues without problem.

However if a second instance of this class above is instantiated, _businessObjects has already been populated so _businessObjectTask remains null. Then when _businessObjectTask.ContinueWith gets called, a NRE on _businessObjectTask is thrown.

There were a couple of options I could have taken but I ended up removing the _businessObjectTask to a synchronous method call which means that I dont need to use the continuation anymore and I set _businessObjects once.

解决方案

This is a synchronization problem.

You are assuming that _businessObjectTask is always assigned before _businessObjects.

That is, however, not guaranteed. It is possible that your continuation that assign _businessObjects is executed inline and therefore before businessObjectService.GetData(...).ContinueWith(...) returns.

// 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);              

Therefore, it is possible that _businessObjects is not null although _businessObjectTask is null.

If a concurrent thread would enter your GetData method at that time it would clearly not enter

if (_businessObjects == null) // not entered because it's not null
{
    ...
}

...and instead continue with

var taskSetLEData = _businessObjectTask.ContinueWith // line 748

...which will cause a null reference exception since _businessObjectTask is null.


Here's how you could simplify your code and resolve this synchronization problem:

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;
        }
    );
}

Notes:

  • Using Lazy<T> to ensure that the business object service is only invoked once (per instance of this class, whatever it is).

  • Using async/await to simplify code.

  • You may want to consider declaring _lazyTask as static. In your code there seem to be a mixup between static/non-static fields. I cannot know which is right for you.

这篇关于空引用-任务ContinueWith()的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆