这很奇怪,我不明白这里发生了什么。我有一个 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))
and if id
为空,那么我会从 api 收到错误的请求,因为它不会映射,加上我的 linq 语句来获取具有匹配的类型GUID
会返回 null,我也在测试它。而如果param
为空,没关系,您可以将字典条目设置为空值。
感觉就像是某种未完全初始化的竞争条件,但我看不到它来自哪里或如何防御它。
下面是它(偶尔)抛出的示例(作为 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)"
}
The line 85
是我上面突出显示的那一行。
这是一种海森巴格,但我确实设法通过立即在我的 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")});
快速连续发出四个请求,其中两个在同一点失败,但这就是真正令人恼火的地方,它在那条线上中断,我可以将鼠标悬停在上面cache
, id
and param
在 Visual Studio 中查看它们的当前值和none其中都为空!并且在每种情况下cache
has a Count
1,这也很奇怪,因为这应该是由 Ninject 创建的单例,所以我开始怀疑 Ninject 有什么问题。
作为参考,在 NinjectWebCommon 中,我注册此服务如下:
kernel.Bind<Services.IMyFinderService>().To<Services.MyFinderService>().InSingletonScope();
Note: 这个我也发过单例和竞争条件因为我不能 100% 确定问题不是 Ninject,但我不想将这个问题与太多关于什么的猜想混淆might成为原因。