gpt4 book ai didi

c# - 空引用 - 任务 ContinueWith()

转载 作者:行者123 更新时间:2023-11-30 17:40:36 25 4
gpt4 key购买 nike

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

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

GetData() 可以被调用多次,因此 _businessObjectTask 的第一个延续将只被调用一次,因为 _businessObjects 将从那时起被填充。

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

  1. _businessObjectTask 为空,无法从空任务继续。
  2. items 作为参数传入的变量不知何故为空

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

如有任何帮助,我们将不胜感激。

编辑:这与 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() 时为空,然后将 _businessObjectTask 设置为非空。然后,当 _businessObjectTask.ContinueWith 被调用时,它是非空的并且继续没有问题。

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

我可以采取几个选项,但我最终将 _businessObjectTask 移除到同步方法调用,这意味着我不再需要使用延续,我设置了 _businessObjects 一次。

最佳答案

这是一个同步问题。

您假设 _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
{
...
}

...然后继续

var taskSetLEData = _businessObjectTask.ContinueWith // line 748

...自 _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作为静态。在您的代码中,静态/非静态字段之间似乎存在混淆。我不知道哪个适合你。

关于c# - 空引用 - 任务 ContinueWith(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34177032/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com