web api调用中的空引用

时间:2014-05-01 13:42:47

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

这是一个奇怪的,我不知道这里发生了什么。我有一个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 - 而奇怪的部分是,它声称源是这一行:< / p>

cache[id] = param;

但问题是,如果cache为空(不可能,它在构造函数中设置,它是私有的,这是唯一的方法,甚至触及它),它应该抛出:

if (cache.ContainsKey(id))

如果id为null,那么我会得到一个来自api的错误请求,因为它不会映射,加上我的linq语句来获取匹配GUID的类型会返回null,我也在测试。如果param为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")});

快速连续发出四个请求,其中两个在同一点失败,但是这里真的很令人气愤,它突破了那条线,我可以将鼠标悬停在cacheid上在Visual Studio中param,看看它们的当前值和它们的 none 是否为空!并且在每种情况下cache的{​​{1}}都是1,这也是奇怪的,因为这应该是由Ninject创建的单例,所以我开始怀疑Ninject有些麻烦。

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

Count

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

1 个答案:

答案 0 :(得分:3)

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

查看堆栈跟踪,在 Dictionary的内部Insert方法中抛出异常。所以cache绝对不是空的。 id也不能为空,因为Guid是值类型。字典中允许空值,因此如果param为空则无关紧要。问题是,一个线程可能正处于更新的中间,导致字典重新分配其内部存储桶,而另一个线程尝试插入另一个值。某些内部状态不一致而且会抛出。我打赌这种情况偶尔会在初始化过程中发生,因为当缓存容易被填满时会发生这种情况。

你需要使这个类是线程安全的,这意味着锁定对字典的访问,或者(更简单)使用设计为线程安全的类ConcurrentDictionary