【问题标题】:Null reference in web api callWeb api 调用中的空引用
【发布时间】:2014-06-18 00:05:05
【问题描述】:

这是一个奇怪的问题,我不知道这里发生了什么。我有一个 web api 项目,在一个控制器中,对某个方法的调用最终会调用服务中的一个函数,如下所示:

    public MyClassBase GetThing(Guid id)
    {
        if (cache.ContainsKey(id))
        {
            return cache[id];
        }
        else
        {
            var type = typeof(MyClassBase).Assembly.GetTypes().FirstOrDefault
            (
                t => t.IsClass &&
                    t.Namespace == typeof(MyClassBase).Namespace + ".Foo" &&
                    t.IsSubclassOf(typeof(MyClassBase)) &&
                    (t.GetCustomAttribute<MyIdAttribute>()).GUID == id
            );
            if (type != null)
            {
                System.Diagnostics.Debug.WriteLine(string.Format("Cache null: {0}",cache == null));
                var param = (MyClassBase)Activator.CreateInstance(type, userService);
                cache[id] = param;
                return param;
            }
            return null;
        }
    }

cache 只是一个字典:

 protected Dictionary<Guid, MyClassBase> cache { get; set; }

在这个类的构造函数中创建:

 cache = new Dictionary<Guid, MyClassBase>();

这在 99.9% 的情况下都能完美运行,但有时,当第一次启动应用程序时,第一个请求会抛出一个 NullReferenceException - 奇怪的是,它声称来源是这一行:

cache[id] = param;

但问题是,如果cache 为 null(这是不可能的,它是在构造函数中设置的,它是私有的,这是 only 甚至触及它的方法) ,那么它应该扔在:

if (cache.ContainsKey(id))

如果id 为空,那么我会收到来自 api 的错误请求,因为它不会映射,加上我的 linq 语句以获取具有匹配 GUID 的类型将返回空,我'我也在测试。如果param 为null,那也没关系,您可以将字典条目设置为null 值。

这感觉像是一个没有完全初始化的东西的竞争条件,但我看不出它来自哪里或如何防御它。

这是它(偶尔)抛出的一个示例(作为 JSON,因为 web api 返回 json 并且我目前得到它向我吐回错误消息,以便我可以找到它们):

{
    "message": "An error has occurred.",
    "exceptionMessage": "Object reference not set to an instance of an object.",
    "exceptionType": "System.NullReferenceException",
    "stackTrace": "   at System.Collections.Generic.Dictionary`2.Insert(TKey key, 
        TValue value, Boolean add)\r\n   at 
        System.Collections.Generic.Dictionary`2.set_Item(TKey key, TValue value)\r\n   
        at MyNameSpace.Services.MyFinderService.GetThing(Guid id) in
        c:\\...\\MyFinderService.cs:line 85\r\n   
        at MyNameSpace.Controllers.MyController.GetMyParameters(Guid id) in 
        c:\\...\\Controllers\\MyController.cs:line 28\r\n   at 
        lambda_method(Closure , Object , Object[] )\r\n   at
        System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass13.
        <GetExecutor>b__c(Object instance, Object[] methodParameters)\r\n   at
        System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.Execute(Object instance,
        Object[] arguments)\r\n   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.
        <>c__DisplayClass5.<ExecuteAsync>b__4()\r\n   at
        System.Threading.Tasks.TaskHelpers.RunSynchronously[TResult](Func`1 func, CancellationToken
        cancellationToken)"
}

line 85 是我在上面突出显示的行。

有点像Heisenbug,但我确实设法让它立即在我的 html 页面上执行此操作(当然,第二次执行它就可以了):

    $.ajax({
        url: "@Url.Content("~/api/MyController/GetMyParameters")",
    data: { id: '124c5a71-65b7-4abd-97c0-f5a7cf1c4150' },
    type: "GET"
    }).done(function () { console.log("done"); }).fail(function () { console.log("failed") });

    $.ajax({
        url: "@Url.Content("~/api/MyController/GetMyParameters")",
        data: { id: '7cc9d80c-e7c7-4205-9b0d-4e6cb8adbb57' },
    type: "GET"
    }).done(function () { console.log("done"); }).fail(function () { console.log("failed") });

    $.ajax({
        url: "@Url.Content("~/api/MyController/GetMyParameters")",
        data: { id: '1ea79444-5fae-429c-aabd-2075d95a1032' },
    type: "GET"
    }).done(function () { console.log("done"); }).fail(function () { console.log("failed") });

    $.ajax({
        url: "@Url.Content("~/api/MyController/GetMyParameters")",
        data: { id: 'cede07f3-4180-44fe-843b-f0132e3ccebe' },
    type: "GET"
    }).done(function() { console.log("done"); }).fail(function() { console.log("failed")});

快速连续发出四个请求,其中两个在同一点失败,但这里真的很令人生气,它打破了那条线,我可以将鼠标悬停在 cacheidparam 上Visual Studio 并查看它们的当前值,并且 none 为空!在每种情况下,cacheCount 为 1,这也很奇怪,因为这应该是由 Ninject 创建的单例,所以我开始怀疑 Ninject 有什么问题。

作为参考,在 NinjectWebCommon 中,我正在像这样注册此服务:

kernel.Bind<Services.IMyFinderService>().To<Services.MyFinderService>().InSingletonScope();

注意:我也发布了这个Singletons and race conditions,因为我不能 100% 确定问题不是 Ninject,但我不想将这个问题与太多关于什么的猜想混淆可能是原因。

【问题讨论】:

  • 您确定它是NullReferenceException 而不是ArgumentNullException?如果在调用cache[id]idnull,您将得到一个ArgumentNullExceptionmsdn.microsoft.com/en-us/library/9tee9ht2.aspx
  • 我怀疑这是因为未定义 userService 而引发的,导致激活器失败,并导致在抛出错误的行中未定义参数。
  • @EvanMulawski:不,绝对是NullReferenceException。我在上面添加了一个示例。
  • @theDarse:我也怀疑过(尤其是userService 是由 ninject 注入到构造函数中的),但似乎并非如此。首先,如果上一行无法构造它,我希望它会抛出。其次,我检查了可以由这段代码构造的每个类,他们对userService 参数所做的唯一事情是将其存储在一个变量中以供以后使用。所以将 null 传递给他们应该是一个问题——至少现在还没有。第三,myDictionary[someKey] = null 无论如何都是完全有效的代码。
  • @MattBurland:那么cache 肯定是null。您是否在本地运行 API?如果是这样,您可以运行+调试它并设置断点。我会在第 85 行设置断点,然后在构造函数中初始化 cache

标签: c# asp.net-web-api ninject nullreferenceexception


【解决方案1】:

问题在于Dictionary 不是线程安全的。

查看堆栈跟踪,内部 Dictionary 的内部Insert 方法抛出了异常。所以cache 绝对不是空的。 id 也不能为 null,因为 Guid 是一个值类型。字典中允许空值,因此param 是否为空并不重要。问题是一个线程可能处于更新的中间,导致字典重新分配其内部存储桶,而另一个线程尝试插入另一个值。某些内部状态不一致并抛出。我敢打赌这在初始化期间偶尔会发生一次,因为那是缓存往往会被填满的时候。

您需要使此类成为线程安全的,这意味着锁定对字典的访问,或者(甚至更容易)使用像ConcurrentDictionary 这样被设计为线程安全的类。

【讨论】:

  • 天哪 - 这是您立即知道必须正确的答案之一。我只是测试了它可能十几次,它还没有抛出任何东西。谢谢。
  • 另外,堆栈跟踪告诉我错误来自内部的Dictionary,这一点非常棒。我真的应该发现这一点。
猜你喜欢
  • 2012-04-03
  • 2020-12-18
  • 1970-01-01
  • 1970-01-01
  • 2015-11-05
  • 2022-11-22
  • 2015-01-15
  • 2015-09-01
  • 1970-01-01
相关资源
最近更新 更多