首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >空引用-任务ContinueWith()

空引用-任务ContinueWith()
EN

Stack Overflow用户
提问于 2015-12-09 10:56:31
回答 1查看 1.7K关注 0票数 3

对于下面的代码(.NET v4.0.30319),我将得到下面第二个延续中指示的空引用异常。

最有趣的是,这个问题只发生在有8GB RAM的机器上,但其他用户有16 8GB或更多的内存,而且他们没有报告任何问题,这是一个非常间歇性的问题,使我怀疑垃圾收集问题。

可以多次调用GetData(),因此_businessObjectTask的第一个延续将只被调用一次,因为从这一点开始已经填充了_businessObjects。

我想抛出Object reference not set to an instance of an object异常是因为

  1. _businessObjectTask是空的,它不能从空任务继续。
  2. 作为参数传入的items变量在某种程度上是空的。

日志文件( 748 )中的行号指向下面突出显示的一行,而不是指向上面#1而不是#2的lambda表达式。

任何帮助都将不胜感激。

编辑:这与What is a NullReferenceException, and how do I fix it?无关,因为这是对空引用的一个更基本的解释,而这却要复杂得多,也更加微妙。

异常

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

代码语言:javascript
复制
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()

代码语言:javascript
复制
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

EN

回答 1

Stack Overflow用户

发布于 2015-12-09 13:50:59

这是一个同步问题。

您假设_businessObjectTask总是在_businessObjects之前被分配。

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

代码语言:javascript
复制
// 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方法,那么它显然不会输入

代码语言:javascript
复制
if (_businessObjects == null) // not entered because it's not null
{
    ...
}

相反,...and继续使用

代码语言:javascript
复制
var taskSetLEData = _businessObjectTask.ContinueWith // line 748

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

下面是如何简化代码并解决这个同步问题的方法:

代码语言:javascript
复制
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声明为静态的。在您的代码中,似乎在静态/非静态字段之间出现了混淆。我不知道哪一个适合你。
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/34177032

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档