在网络API调用空引用 [英] Null reference in web api call

查看:212
本文介绍了在网络API调用空引用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是一个奇怪的一个,我不知所措我明白了什么是怎么回事。我有一个Web API项目,在一个控制器有一定方法的调用最终看起来像这样一个服务调用一个函数:

 公共MyClassBase GetThing(GUID ID)
    {
        如果(cache.ContainsKey(ID))
        {
            返回高速缓存[ID]
        }
        其他
        {
            VAR类型= ty​​peof运算(MyClassBase).Assembly.GetTypes()。FirstOrDefault
            (
                T => t.IsClass&功放;&安培;
                    t.Namespace == typeof运算(MyClassBase).Namespace +.foo此时&功放;&安培;
                    t.IsSubclassOf(typeof运算(MyClassBase))及和放大器;
                    (t.GetCustomAttribute&所述; MyIdAttribute>())。GUID ==标识
            );
            如果(型!= NULL)
            {
                System.Diagnostics.Debug.WriteLine(的String.Format(缓存无效:{0},缓存== NULL));
                VAR参数=(MyClassBase)Activator.CreateInstance(类型,userService);
                缓存[ID] =参数;
                返回参数;
            }
            返回null;
        }
    }

缓存是一个简单的字典:

 保护字典< GUID,请MyClassBase>缓存{搞定;组; }

这被在构造该类创建:

 缓存=新词典< GUID,请MyClassBase>();

这完美的作品中99.9%的时间,但偶尔,当第一次启动应用程序,第一个请求将抛出一个的NullReferenceException - 和怪异的一部分,它声称源是这一行:

 缓存[ID] =参数;

但事实是,如果缓存为空(这是不能,它在构造函数中设置,它是私有的,这就是的只有的方法,即使接触它),那么它应该在抛出:

 如果(cache.ContainsKey(ID))

如果 ID 为空,那么我会得到了来自API,因为它不会地图,再加上我的LINQ语句错误请求用得到的类型匹配 GUID 将返回null,这我也测试了。如果参数为空,它不应该的问题,你可以设置一个字典项为空值。

这感觉就像有东西的竞争条件没有被完全初始化,但我不能看到它来自哪里或如何抵御它。

下面是什么(偶尔)抛出一个例子(如JSON,因为Web API返回的JSON,我已经得到了电流吐出这回我的错误信息,所以我可以找到它们):

  {
    消息:发生了错误。
    exceptionMessage:未将对象引用设置到对象的实例。
    exceptionType:System.NullReferenceException,
    堆栈跟踪:在System.Collections.Generic.Dictionary`2.Insert(TKEY的关键,
        TValue值,布尔加)\\ r \\ n在
        System.Collections.Generic.Dictionary`2.set_Item(TKEY的关键,TValue值)\\ r \\ n
        在MyNameSpace.Services.MyFinderService.GetThing(GUID ID)的
        C:\\\\ \\\\ ... MyFinderService.cs:行85 \\ r \\ n
        在MyNameSpace.Controllers.MyController.GetMyParameters(GUID ID)的
        C:\\\\ \\\\ ... \\\\控制器MyController.cs:第28行\\ r \\ n在
        lambda_method(封闭,对象,对象[])\\ r \\ n在
        System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor<> c__DisplayClass13。
        < GetExecutor> b__c(Object实例,对象[] methodParameters)\\ r \\ n在
        System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.Execute(Object实例,
        [对象]参数)\\ r \\ n在System.Web.Http.Controllers.ReflectedHttpActionDescriptor。
        <> c__DisplayClass5< ExecuteAsync> b__4(个)\\ r \\ n在
        System.Threading.Tasks.TaskHelpers.RunSynchronously [TResult](Func`1 FUNC键的CancellationToken
        的CancellationToken)
}

85行是我上文所强调的就行了。

这是怎样的一个 Heisenbug 的,但我没有只是管理得到它做扔这马上我的html页面上(当然,这样做它的工作就好了第二次):

  $阿贾克斯({
        网址:@ Url.Content(〜/ API / myController的/ GetMyParameters),
    数据:{ID:'124c5a71-65b7-4abd-97c0-f5a7cf1c4150},
    键入:GET
    })来实现(函数(){的console.log(完成);})失败(函数(){的console.log(失败)});    $阿贾克斯({
        网址:@ Url.Content(〜/ API / myController的/ GetMyParameters),
        数据:{ID:'7cc9d80c-e7c7-4205-9b0d-4e6cb8adbb57},
    键入:GET
    })来实现(函数(){的console.log(完成);})失败(函数(){的console.log(失败)});    $阿贾克斯({
        网址:@ Url.Content(〜/ API / myController的/ GetMyParameters),
        数据:{ID:'1ea79444-5fae-429c-aabd-2075d95a1032},
    键入:GET
    })来实现(函数(){的console.log(完成);})失败(函数(){的console.log(失败)});    $阿贾克斯({
        网址:@ Url.Content(〜/ API / myController的/ GetMyParameters),
        数据:{ID:'cede07f3-4180-44fe-843B-f0132e3ccebe},
    键入:GET
    })来实现(函数(){的console.log(完成);})失败(函数(){的console.log(失败)});

发射了四项要求临门,其中两个没有在同一地点,但这里就是它变得非常真气,它打破了在该行,我可以将鼠标悬停在缓存 ID 参数在Visual Studio中看到它们的当前值和的中他们都为空!而在每种情况下缓存有一个计数 1,这也是奇怪,因为这被认为是创造了单通过Ninject,所以我开始怀疑Ninject东西扭曲。

有关参考,在NinjectWebCommon,我注册这个服务是这样的:

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

注意:我也张贴了这个单身和比赛条件因为我不是100%肯定问题不是Ninject,但我不想有太多的猜测混淆这个问题什么的可能的是原因。


解决方案

的问题是,词典不是线程安全的。

看堆栈跟踪,异常是在的解释的内部插入方法抛出的。因此,缓存绝对不是空。 ID 不能为空或者,因为的Guid 是值类型。空值在字典中允许的,所以如果参数为空不要紧。的问题是,一个线程可能是引起,而另一个线程进入尝试插入另一值的字典重新分配其内部的存储桶的更新的​​中间。一些内部状态是不一致的,它抛出。我敢打赌,这在初始化过程中在一段时间发生一次,因为这是当缓存往往会得到填补。

您需要让这个类是线程安全的,这意味着锁定访问字典或(更容易),使用像 ConcurrentDictionary A类的设计是线程安全的

This is a weird one, and I'm at a loss to understand what's going on here. I have a web api project that in one controller a call to a certain method eventually calls a function in a service that looks like this:

    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 is simply a dictionary:

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

That gets created in the constructor for this class:

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

This works perfectly 99.9% of the time, but occasionally, when first starting up the app, the first request will throw a NullReferenceException - and the weird part is, it claims that the source is this line:

cache[id] = param;

But the thing is, if cache is null (which is can't be, it was set in the constructor, it's private, and this is the only method that even touches it), then it should have thrown at:

if (cache.ContainsKey(id))

and if id was null, then I would have got a bad request from the api because it wouldn't map, plus my linq statement to get the type with a matching GUID would have returned null, which I'm also testing for. And if param is null, it shouldn't matter, you can set a dictionary entry to a null value.

It feels like a race condition with something not being fully initialized, but I can't see where it's coming from or how to defend against it.

Here's an example of what it (occassionally) throws (as JSON because the web api returns json and I've current got it spitting me back error messages so I can find them):

{
    "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)"
}

The line 85 is the line I highlighted above.

It's kind of a Heisenbug, but I did just manage to get it to throw by doing this immediately on my html page (of course, doing it a second time it worked just fine):

    $.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")});

Firing off four requests in quick succession, two of them failed at the same point, but here's where it gets really infuriating, it breaks on that line and I can hover over cache, id and param in Visual Studio and see their current value and none of them are null! And in each case cache has a Count of 1, which is also odd because this is supposed to be a singleton created by Ninject, so I'm starting to suspect something screwy with Ninject.

For reference, in NinjectWebCommon, I am registering this service like this:

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

Note: I also posted this Singletons and race conditions because I'm not 100% sure the problem isn't Ninject, but I didn't want to confuse this question with too many conjectures about what might be the cause.

解决方案

The problem is that Dictionary is not thread-safe.

Look at the stack trace, the exception is thrown inside Dictionary's internal Insert method. So cache is definitely not null. id can't be null either, because Guid is a value type. Null values are allowed in a dictionary so it doesn't matter if param is null. The problem is that one thread is probably in the middle of an update that caused the dictionary to reallocate its internal buckets while another thread goes tries to insert another value. Some internal state is inconsistent and it throws. I'd bet this happens once in a while during initialization because that's when caches tend to get filled.

You need to make this class thread-safe which means locking access to the dictionary or (even easier) using a class like ConcurrentDictionary that is designed to be thread-safe.

这篇关于在网络API调用空引用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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